So Im doing an app where the user can upload photos, each photo has a user, image, description, and other fields.
When the user creates a new photo, the image is required, but when the user wants to update it, you only can change the description. I'm stuck with this, first tried with update (put) but it's clearly not the method to use, now I'm trying with partial_update but I can't get it right.
This is what I have right now, it gives me the error: TypeError: Object of type Photo is not JSON serializable.
Maybe it's a completely wrong way of doing it.
View:
class PhotoViewSet(mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
""" PhotoViewSet
Handle upload (create) and delete of photos.
"""
queryset = Photo.objects.all()
serializer_class = PhotoModelSerializer
def perfom_create(self, serializer):
""" Upload a new photo. """
serializer.save()
def perform_destroy(self, instance):
""" Delete a photo. """
return super().perform_destroy(instance)
def retrieve(self, request, *args, **kwargs):
""" Retrieve photo information. """
response = super(PhotoViewSet, self).retrieve(request, *args, **kwargs)
data = {
'photo': response.data,
}
response.data = data
return response
def partial_update(self, request, pk=None):
serializer = UpdateDescriptionSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
data = serializer.save()
return Response(data=data, status=status.HTTP_202_ACCEPTED)
serializer:
class UpdateDescriptionSerializer(serializers.ModelSerializer):
""" Update description serializer. """
description = serializers.CharField(max_length=255)
class Meta:
""" Meta class. """
model = Photo
fields = ('description',)
You should pass the instance that you wish to update. Furthermore you should not return the outcome of the .save() method, but the data of the serializer, so:
def partial_update(self, request, pk=None):
instance = self.get_object()
serializer = UpdateDescriptionSerializer(
instance,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(data=serializer.data, status=status.HTTP_202_ACCEPTED)
If you want to return all the details of the updated item, you can work with another serializer that will create the data for the response, so:
def partial_update(self, request, pk=None):
instance = self.get_object()
serializer = UpdateDescriptionSerializer(
instance,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
serializer2 = PhotoModelSerializer(instance)
return Response(data=serializer2.data, status=status.HTTP_202_ACCEPTED)
You should use 2 different serializers, of which 1 of them is dedicated to update your object e.g. PhotoSerializer and UpdatePhotoSerializer.
What I would suggest is to override the get_serializer_class() method as follows:
def get_serializer_class(self):
if self.request.method == "PATCH": # add PUT too if you wish
return UpdatePhotoSerializer
return PhotoSerializer
Another tip, using generic views is much cleaner in your views as per documentation.
Related
I try to make a custom list function inside ProductViewSet because I need to download an extra field - the highest product price in database. How can I provide the request argument from def list into serializer? I mean right now I get error 'NoneType' object has no attribute 'user' in this line: if request.user.is_authenticated.
So how can I fix it that he can read self.context.get('request') correctly?
class ProductViewSet(viewsets.ModelViewSet):
...
def list(self, request):
queryset = Product.objects.all()
serializer = ProductSerializer(queryset, many=True)
return Response(serializer.data)
class ProductSerializer(serializers.ModelSerializer):
...
class Meta:
model = Product
...
def get_is_followed(self, obj):
request = self.context.get('request')
if request.user.is_authenticated:
return Product.objects.filter(id=obj.id, followers=request.user.id).exists()
I want to work it like a default ModelViewSet list but with an extra field.
You have used ModelViewSet which already has a serializer_class attribute. You can simply provide the serializer_class and the serializer context is taken care by DRF itself. So, instead of writing
serializer = ProductSerializer(queryset, many=True) you should write in this way:
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
If your concern is only the request object as a context to the serializer then there is no need to override the list method in the ProductViewSet. By default three contexts (request, format and view) are passed to the serializer but if you need extra contexts then you can always override def get_serializer_context(self) method in the view.
This is the default signature of the 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
}
I'm using djangorestframework together with drf-spectacular modules for a Django project, and I'm trying to build some basic API methods for my Project model. Its structure looks like this:
from django.db import models
# Create your models here.
class Project(models.Model):
title = models.CharField(max_length = 128)
description = models.TextField()
image = models.URLField()
date = models.DateTimeField(auto_now_add=True)
I also have a serializer for the model, looking like this:
from rest_framework import serializers
from api.models.Project import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['title', 'description', 'image', 'date']
Then, in views.py, I created two functions: project_list_view, which either lets you to GET all the Project objects from the database, or lets you POST a new object. And finally, project_detail_view, which lets you GET a Project object by typing in its pk (integer id). These are my two functions:
#api_view(['GET', 'POST'])
def project_list_view(request):
if request.method == 'GET':
projects = Project.objects.all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = ProjectSerializer(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)
#api_view(['GET'])
def project_detail_view(request, pk):
if request.method == "GET":
try:
project = Project.objects.get(pk = pk)
serializer = ProjectSerializer(project, many = False)
return Response(serializer.data, status = status.HTTP_200_OK)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
The GET from project_list_view and project_detail_view work, but my problem lays in the POST method.
My Swagger is set to display its API Schema when accessing http://127.0.0.1:8000/docs/, and as I said, GET methods work properly, but when I'm trying to click on "Try it out" at the POST method, the fields are not displayed. I can only press "Execute" without actually being able to complete anything. After I click on "Execute", Swagger returns a 404 Bad Request response.
This is how POST looks like in Swagger:
My question is: Why won't Swagger display fields for each parameter of the model? Thank you.
Swagger Grabs the fields from a serializer_class variable.
I really recommend you change the format to Class-Based Views.
Something using mixins or generic class.
Your view could be like
class ProjectView(mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = ProjectSerializer
queryset = Project.objects.all()
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
More on Mixins and Generic Views
I am trying to do a partial update of a user so that not all the fields should be sent in a request. For this I have created a view like this:
elif request.method == 'PATCH':
user_serializer = UserSerializer(user, data=request.data, partial=True)
if user_serializer.is_valid():
user_serializer.save()
return Response({'message': 'User updated correctly'}, status=status.HTTP_200_OK)
else:
return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have seen that if the partial = True parameter is activated then it will call the partial_update () function of the serializer, but it does not:
def partial_update(self, request, *args, **kwargs):
print("partial update")
So, how can I do this partial_update to update a user field?
Try assigning the serializer to the view:
serializer_class = UserSerializer
Then you can get the serializer from the view instance:
user_serializer = self.get_serializer(user, data=request.data, partial=True)
user_serializer.save()
Finally partial_update method should be triggered at view level.
Our API has a model defined:
class Job(models.Model):
file = models.FileField('File')
xml = models.FileField('XML')
There is a basic serializer:
class XmlSerializer(serializers.ModelSerializer)
file = serializers.FileField(read_only=True)
xml = serializers.FileField(required=True)
class Meta:
model = Job
fields = '__all__'
We don't want to change the file but we do want to change the xml field. The xml is uploaded by a system that doesn't know the primary key. Of course we need this to update the model.
I have the following code at the moment:
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
pk = 200
serializer = XmlSerializer(request.data)
return Response({})
pk = 200 serves as an example instead of the code we use to parse the xml. I know this doesn't work but it's to show my intention (more or less).
I have tried to use
id = serializers.SerializerMethodField()
def get_id(self, obj):
return 200
without success.
How do I get this primary key into the the serializer?
I was making it much too difficult. The solution was pretty easy.
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
id = magic_xml_parser_function()
job = get_object_or_404(Job, pk=id)
serializer = XmlSerializer(job, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
magic_xml_parser_function() contains the id we found in the xml. This solved it for us.
I'd like to create a REST API for an object which can be partially updated. On http://www.django-rest-framework.org/api-guide/serializers/#partial-updates and example is given in which partial=True is passed when instantiating the serializer:
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
In my case, however, the model (which is called SessionType) has the following viewset:
class SessionTypeViewSet(viewsets.ModelViewSet):
queryset = SessionType.objects.all()
serializer_class = SessionTypeSerializer
where the serializer is defined as
class SessionTypeSerializer(serializers.ModelSerializer):
class Meta:
model = SessionType
fields = ('title',)
How can I adapt the serializer in this use case so that partial is always True?
You don't need to adapt the serializer in any way. With that viewset, any call to the "detail" endpoint using the PATCH method will do a partial update.
The Django Rest Framework ModelViewSet base class includes the following mixin. Here you can see how partial=True is passed when calling partial_update, which is routed to the PATCH method by default:
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)
The partial update is implicit in the ModelViewset acoording with the documentation the only thing you need to do is call the "SessionTypeViewSet" endpoint with the method PATCH