Django rest framework partial update (PATCH) - python

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.

Related

How to get the authentication information in ModelViewSet?

I am using rest framework with ModelViewSet
class DrawingViewSet(viewsets.ModelViewSet):
queryset = m.Drawing.objects.all()
serializer_class = s.DrawingSerializer
filterset_fields = ['user']
def list(self, request):
queryset = m.Drawing.objects.all()
serializer = s.DrawingSerializer(queryset, many=True)
return Response(serializer.data)
With this script, I can use filter such as /?user=1
Howver this user=1 is not necesary when authentication information is used in script.
(Because one user needs to fetch only data belonging to him/herself)
How can I get the authentication data in list?
If you want to return data based on user him/herself, there is no need to pass user id. If your user is authenticated, you can have his/her ID via request so:
from rest_framework.permissions import IsAuthenticated
class DrawingViewSet(viewsets.ModelViewSet):
...
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = m.Drawing.objects.filter(user=request.user)
serializer = s.DrawingSerializer(queryset, many=True)
return Response(serializer.data)
yes you can do like this as the above answer or you can override the get_queryset method as well.
def get_queryset(self):
if self.request.user.is_authenticated:
return whatever you want to return
else:
return None

Django - PUT endpoint serializer should ignore missing attributing

so I have this put endpoint
def put(self, request):
user_uuid = get_uuid_from_request(request)
user_obj = User.objects.get(pk=user_uuid)
serializer = UpdateUserSerializer(user_obj, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(dict(error=serializer.errors, user_msg=generic_error_message),
status=status.HTTP_400_BAD_REQUEST)
with the following serializer
class UpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('given_name', 'middle_name', 'family_name', 'birthdate')
if the incoming request has missing values as in
request.data ={'given_name':'Tom'}
I'd ideally like it to update anything that isn't missing in the current entity. How do I do this? Currently right now when I test it if an attribute is missing it complains.
You can add the flag partial=True to the serializer to support partial updates:
serializer = UpdateUserSerializer(user_obj, data=request.data, partial=True)

How to allow the update of only 1 field?

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.

Django Rest: passing queryset to hyperlinked model serializer

class JobViewSet(viewsets.ModelViewSet):
queryset = models.Job.objects.all()
serializer_class = serializers.JobSerializer
#action(methods=["get"], detail=True, url_path="like-detail", url_name="like-detail")
def list_likes(self, request, pk=None):
job = self.get_object()
queryset = job.likes.all()
serializer = UserSerializer(queryset, many=True, context={"request": request})
if serializer.is_valid():
return Response(serializer)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Error:
AssertionError at /jobs/12/like-detail/
Cannot call .is_valid() as no data= keyword argument was passed when instantiating the serializer instance.
Not sure how can i pass data arg because is have a queryset, i tried with .values() and .values_list() but that don't solve the purpose because then no hyperlink (url) will be available of UserSerializer.

django rest framework error Cannot apply DjangoModelPermissions on a view that does not set `.queryset` or have a `.get_queryset()` method

i am using django 1.9.5 and rest framework 3.x(DRF).I have just following the tutorial from official django rest framework,you can say its getting start wiht DRF,i have write following views , urls to see how api works using DRF,
views
class DepartMentList(APIView):
"""
List of all departments or create a department
"""
def get(self, request, format=None):
departments = Department.objects.all()
serializer = DepartmentSerializer(departments)
return Response(serializer.data)
def post(self, request, format=None):
serializer = DepartmentSerializer(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)
urls
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from organizations import views
urlpatterns = [
url(r'^departments/$', views.DepartMentList.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
and this is the setting.py where i have added the following rest framework dict for DEFAULT_PERMISSION_CLASSES
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
now when i run the endpoint for department to see the list of departments,then i am getting this following error,
'Cannot apply DjangoModelPermissions on a view that '
AssertionError: Cannot apply DjangoModelPermissions on a view that does not set `.queryset` or have a `.get_queryset()` method.
what does actually causes the error? i have investigated,but can't figure it out.
UPDATE
class DepartMentDetail(APIView):
"""
Retrieve, update or delete a department instance.
"""
def get_object(self, pk):
try:
return Department.objects.get(pk=pk)
except Department.DoesNotExist:
raise Http404
def get(self,request,pk,format=None):
department = self.get_object(pk)
serializer = DepartmentSerializer(department)
return Response(serializer.data)
def put(self,request,pk,format=None):
department = self.get_object(pk)
serializer = DepartmentSerializer(department,data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
department = self.get_object(pk)
department.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
DjangoRestFramework requiries you to set queryset class argument or implement get_queryset method on your view. It checks it when applying permission class. Because DjangoModelPermissionsOrAnonReadOnly has has_permission method as shown below, and this method check if your view has queryset variable or get_queryset method.
def has_permission(self, request, view):
# Workaround to ensure DjangoModelPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not set `.queryset` or have a `.get_queryset()` method.'
)
perms = self.get_required_permissions(request.method, queryset.model)
return (
request.user and
(request.user.is_authenticated() or not self.authenticated_users_only) and
request.user.has_perms(perms)
)
as you see has_permission method makes assert for queryset variable
Your view should look like this
class DepartMentList(APIView):
"""
List of all departments or create a department
"""
queryset = Department.objects.all()
def get(self, request, format=None):
serializer = DepartmentSerializer(self.queryset)
return Response(serializer.data)
def post(self, request, format=None):
serializer = DepartmentSerializer(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)
P.S use http://www.django-rest-framework.org/api-guide/generic-views/ it is much cleaner))

Categories