Django Rest: passing queryset to hyperlinked model serializer - python

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.

Related

Provide request data from view into serializer ModelViewSet Django

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
}

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)

Django rest framework partial update (PATCH)

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.

How to filter through the list/retrieve function on Model ViewSet

Anyone know How can I implement a search filter on list/retrieve function to a viewset?
I'm trying to use DRF Searh-filter on ViewSet but it's not working (it doesn't return the object filtered).
What I want to return is something like --> /store/1/locker/1/controller?controller={name_controller}
view.py
class ControllerViewSet(viewsets.ModelViewSet):
serializer_class = ControllerSerializer
queryset = Controller.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['controller']
def list(self, request, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(locker__store=store_pk, locker=locker_pk)
serializer = ControllerSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None, store_pk=None, locker_pk=None):
queryset = Controller.objects.filter(pk=pk, locker=locker_pk, locker__store=store_pk)
locker = get_object_or_404(queryset, pk=pk)
serializer = ControllerSerializer(locker, context={'request': request})
return Response(serializer.data)
You set a filter backend, but then you overrode the code that would have called it (list(), retrieve()).
Seems like the only reason you overrode those methods is to filter the queryset by store and locker pks. This can be done in one place to affect all requests and without breaking existing code.
def get_queryset(self):
locker_pk = self.kwargs["locker_pk"] # named parameters in url appear in self.kwargs
store_pk = self.kwargs["store_pk"]
return super().get_queryset().filter(locker=locker_pk, locker__store=store_pk)
And that's it. DRF will call this method to get queryset, filter it as configured, and serialize the data with your serializer automatically. No need to manually implement what's already provided.
Note: ensure your pks are valid with regex. You don't want your app to crash if someone requests /store/x/locker/y/controller, right? The regex should be \d+ for any integer.

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