action decorator not calling method in DRF? - django

so I have a route api/reviews it works fine for basic CRUD operations but according to DRF documentation, I can add extra actions to post, put, patch requests by using the #action decorator.
the route is:
POST domain/api/reviews data=irrelevent JSON
# reviews/<uuid:id> && reviews
# Provides CRUD + List interface for Reviews
class ReviewView(viewsets.ModelViewSet):
permission_classes = [IsPosterOrSafeOnly]
queryset = Review.objects.all()
serializer_class = ReviewSerializer
pagination_class = Pagination
#action(detail=False, methods=['post','put', 'patch'], serializer_class=ReviewSerializer)
def rating(self, request):
print("100: ", request)
from what I understand the way this works is that when I do a post request to the reviews route it will do the post like it normally does and it will do the task in the action decorator.
so I tried doing a post request to see if the code inside the 'rating' method gets run and it does not. I have tried setting detail to both True and False including the pk. nothing really seems to work.

Related

Can you get the request method in a DRF ModelViewSet?

I am building a Django chat app that uses Django Rest Framework. I created a MessageViewSet that extends ModelViewSet to show all of the message objects:
class MessageViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer
This chat app also uses Channels and when a user sends a POST request, I would like to do something channels-realted, but I can't find a way to see what kind of request is made. Is there any way to access the request method in a ModelViewSet?
Rest Framework viewsets map the http methods: GET, PUT, POST, and DELETE to view methods named list, update, create, and destroy respectively; so in your case, you would need to override the create method:
class MessageViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer
def create(self, request):
print('this is a post request', request)
...
we can't get 'GET' request because ModelViewSet is inherited from
mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet
these mixins so instead of 'GET' request we can override list method
enter image description here

DRF 3.6: How to document input parameters in APIView (for automatic doc generation)?

I'm struggling with DRF 3.6 auto-generated interactive documentation to provide input parameters to fill in interactive mode.
As a result, I get an empty windows for my POST request (which would require 3 parameters actually):
With Swagger, I could do it directly in docstring with some YAML.
Now, after browsing DRF documentation, I can't find the way to do it.
class ActivateCustomerView(APIView):
permission_classes = (AllowAny,)
def post(self, request):
""" View dedicated to activating a pre-recorded customer
# Should I add some parameters here?
"""
serializer = ActivateCustomerSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# ...
I Got the answer from Tom Christie:
serializer_class by itself isn't enough - the view needs to implement get_serializer, see: https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas.py#L570
So in My case, adding this works well:
def get_serializer(self):
return ActivateCustomerSerializer()
EDIT: I forgot to answer regarding the input parameters. I believe that would be based on the serializer. Have you tried specifying your serializer_class?
With DRF's inbuilt documentation generator, you'd want to put your docstrings on the class level and include the request method as such:
class ActivateCustomerView(APIView):
"""
post:
View dedicated to activating a pre-recorded customer
# Should I add some parameters here?
# if you have a get request
get:
# your docs for the get handler
"""
permission_classes = (AllowAny,)
def post(self, request):
serializer = ActivateCustomerSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# ...

Problems with Django rest-framework DjangoModelPermissions allowing any authenticated user

I am trying to use DjangoModelPermissions and it does not seem to work properly.
This is the code:
class TestViewSet(viewsets.ModelViewSet):
model = Test
serializer_class = serializers.TestSerializer
permission_classes = (permissions.DjangoModelPermissions,)
def create(self, request):
response_data = {}
response_data['type'] = 'error'
data=json.loads(request.raw_post_data)
test = Test.objects.create(name=data['name'],\
description=data['description'],\
start_date=data['start_date'],\
end_date=data['end_date'])
#save changes
test.save()
return Response({'status': 'ok', "result": test.id})
I don't think it makes any difference in this case but I am using django_mongodb_engine.
I have a user that has no permissions, and it is able to create Test instances. On the other hand, how can I block also GET so just users with the right permissions can perform that action?
Thanks
The reason for DjangoModelPermissions is not working here is clearly explained in the DRF docs
"This permission must only be applied to views that have a .queryset property or get_queryset() method."
Check the docs here
The solution is:
Add queryset to your model
class TestViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TestSerializer
permission_classes = (permissions.DjangoModelPermissions,)
queryset = Test.objects.all()
or if you want to override the default queryset method use this method as you like
def get_queryset(self):
return super().get_queryset()
Also, I noticed you don't have to specify the model in your ModelViewSet. If you specify your Test model in TestSerializer you only have to specify the serializer in ModelViewSet that's how DRF works
My problem was the same. The user could create new instance in the database despite of the permission class. I looked into the django-guardian and found that this back-end should be default:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
So I added it in my settings.py file and now it works and a user without a permission cannot create new instance. I hope it helps.
You need to have django-guardian with DRF for DjangoModelPermissions to work correctly. It's also mentioned in the original docs http://www.django-rest-framework.org/api-guide/permissions under the DjangoModelPermissions paragraph
If it still doesn't work as it should then let us know

django-rest disalow get requests for ModelViewSet

I only want data to be sent using POST to this api endpoint. Is there a way to set which request methods are allowed?
class FooViewSet(viewsets.ModelViewSet):
queryset = Foo.objects.all()
serializer_class = FooSerializer
Do you need the whole ViewSet or will a normal GenericView be sufficient? You could use the CreateAPIView.
Example:
class FooCreate(generics.CreateAPIView):
Model = Foo
serializer_class = FooSerializer
EDIT:
If you do need to use a ViewSet, you could create a custom Router that will only handle post requests. Documentation here, including a read-only (i.e. get) example.
There is probably a better solution, but what I did normally is
if request.method != 'POST':
return Http404
but this seems to have the answer you need:
https://docs.djangoproject.com/en/dev/topics/http/decorators/

Using different authentication for different operations in ModelViewSet in Django REST framework

I have the following ModelViewSet
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated, MyUserPermissions)
I want the create method (POST on /users/) to not ask for any authentication. How can I override the authentication_classes in this case? I'm talking about ModelViewSet not generic API views.
I want the create method (POST on /users/) to not ask for any authentication.
Actually that's not quite what you want. You want POST on users to not require any permissions, which will have the effect that either authenticated or unauthenticated requests will succeed.
I'd suggest overriding your permission classes so that they always allow POST requests. Follow the custom permissions documentation for more info on that.
Essentially you'll have something like:
class IsAuthenticatedOrCreate(permissions.IsAuthenticated):
def has_permission(self, request, view):
if request.method == 'POST':
return True
return super(IsAuthenticatedOrCreate, self).has_permission(request, view)
And probably something similar for your other permission class too.