Provide request data from view into serializer ModelViewSet Django - python

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
}

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

How to override POST method for bulk adding - Django Rest Framework

I'm writing a REST API using Django Rest Framework and I would like that one of my routes accepts bulk adding on POST method, to create multiple objects. Others methods (GET, PUT, PATCH, DELETE) will still remain the same, accepting only one at a time.
What I have so far is below and it's currently working just fine for posting one at a time.
In my urls.py:
path('book', books.BookViewSet.as_view()),
books.py:
class BookViewSet(viewsets.ModelViewSet):
serializer_class = BookSerializer
queryset = Book.objects.all()
permission_classes = (IsAuthenticated, )
serializer.py:
class BookSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# I assume this is the method to be overridden to get this
class Meta:
model = Book
fields = ('id', 'name', 'author_id', 'page_number', 'active')
Serializer create method, unfortunatelly creates data object by object.You can override create method of ModelViewSet and after validation use bulk_create method.
def create(self, request, *args, **kwargs):
many = True if isinstance(request.data, list) else False
serializer = BookSerializer(data=request.data, many=many)
serializer.is_valid(raise_exception=True)
author = request.user # you can change here
book_list = [Book(**data, author=author) for data in serializer.validated_data]
Book.objects.bulk_create(book_list)
return Response({}, status=status.HTTP_201_CREATED)

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.

In the Django REST framework, how to allow partial updates when using a ModeViewSet?

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

Return the current user with Django Rest Framework

I am currently developing an API using Django.
However, I would like to create a view that returns the current User with the following endpoint: /users/current/.
To do so, I created a list view and filtered the queryset on the user that made the request. That works, but the result is a list, not a single object. Combined with pagination, the result looks way too complicated and inconsistent compared to other endpoints.
I also tried to create a detail view and filtering the queryset, but DRF complains that I provided no pk or slug.
With something like this you're probably best off breaking out of the generic views and writing the view yourself.
#api_view(['GET'])
def current_user(request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
You could also do the same thing using a class based view like so...
class CurrentUserView(APIView):
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Of course, there's also no requirement that you use a serializer, you could equally well just pull out the fields you need from the user instance.
#api_view(['GET'])
def current_user(request):
user = request.user
return Response({
'username': user.username,
'email': user.email,
...
})
The best way is to use the power of viewsets.ModelViewSet like so:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
pk = self.kwargs.get('pk')
if pk == "current":
return self.request.user
return super().get_object()
viewsets.ModelViewSet is a combination of mixins.CreateModelMixin + mixins.RetrieveModelMixin + mixins.UpdateModelMixin + mixins.DestroyModelMixin + mixins.ListModelMixin + viewsets.GenericViewSet. If you need just list all or get particular user including currently authenticated you need just replace it like this
class UserViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
# ...
Instead of using full power of ModelViewSet you can use mixins. There is RetrieveModelMixin used to retrieve single object just like it is mentioned here - http://www.django-rest-framework.org/api-guide/viewsets/#example_3
class UserViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
def get_object(self):
return self.request.user
If you need also update your model, just add UpdateModelMixin.
If you must use the generic view set for some reason, you could do something like this,
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def get_object(self):
return self.request.user
def list(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
retrieve method is called when the client requests a single instance using an identifier like a primary key /users/10 would trigger the retrieve method normally. Retrieve itself calls get_object. If you want the view to always return the current used then you could modify get_object and force list method to return a single item instead of a list by calling and returning self.retrieve inside it.
I used a ModelViewSet like this:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') == 'current' and request.user:
kwargs['pk'] = request.user.pk
return super().dispatch(request, *args, **kwargs)
Use this way to get logged in user data in django rest framework
class LoggedInUserView(APIView):
def get(self, request):
serializer = UserSerializer(self.request.user)
return Response(serializer.data)
Add the api in urls.py file.
path('logged_in_user', LoggedInUserView.as_view())

Categories