django-rest disalow get requests for ModelViewSet - django

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/

Related

action decorator not calling method in DRF?

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.

Different authentications and permissions in ModelViewSet - Django REST framework

This question is similar to this one: Using different authentication for different operations in ModelViewSet in Django REST framework, but it didn't work for me.
I've got the following viewset:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
permission_classes = [SpecialPermission]
SpecialPermission looks like this:
class SpecialPermission(IsAuthenticated):
def has_permission(self, request, view):
if request.method == 'POST':
return True
return super().has_permission(request, view)
REST framework settings:
"DEFAULT_AUTHENTICATION_CLASSES": ["backend.api.authentication.ExpiringTokenAuthentication"],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
I want to everybody to be able to post to UserViewSet but every other method should require Authentication. However, with the code above I get an Unauthorized Response on post.
What do I need to change?
Although it can be done, this requirement imo does not justify this ifology as auth/user related stuff should be clean and secure.
Instead extract POST method from this viewset to its own class.
class UserViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
permission_classes = [SpecialPermission]
class CreateUserView(CreateAPIView):
serializer_class = UserSerializer
queryset = UserProfile.objects.none()
authentication_classes = []
if you really want to disable authentication in this viewset I'd rather recommend this
def get_authenticators(self):
if self.action == 'create':
return []
return super().get_authenticators()
That's more explicit than your solution.
I figured it out: Making perform_authentication lazy solved my problem. Now I can post but authentication still runs on all other methods where it is needed.
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
pass

How to make a request in DRF that points to another API endpoint in same Django app

very quick question that is how to nest request in Django-rest-framework. I have end point A that I make POST on and want to make another request to point B in it's serializer perform_create method. This API end points are actually written in same Django application.
Serializer for API A
class ReadingCreate(CreateAPIView):
permission_classes = [IsOwnerOrReadOnly]
serializer_class = ReadingCreateSerializer
def perform_create(self, serializer):
#HERE I WANT TO MAKE REQUEST TO POINT B
serializer.save(user_profile= UserProfile.objects.get(user=self.request.user))
I am familiar with library such as request but I hope there is a better way since, I also need to send token for authentication and I am like in same file. This problem seems simple but I clearly don't know how to do it properly.
Update:
To explain more, "request" should find a book based on the isbn that I send it through ReadingCreateSerializer. But first I need to find a book (Google API), then save it to my DB. This needs to be done because book model is independent of UserProfile object and Reading is not (has additional information). That is what my "request" does.
Of course this could be done with two chain requests from client but I don't want that.
Serializer:
class ReadingCreateSerializer(serializers.HyperlinkedModelSerializer):
isbn = serializers.CharField(required=True, max_length=20)
class Meta:
model = Reading
fields = ['isbn', 'notes', 'rating', 'start_reading_date', 'end_reading_date']
What I tried based on the answer: part of view and part of serializer
def perform_create(self, serializer):
self.request.method = 'POST'
serializer.save(user_profile=UserProfile.objects.get(user=self.request.user), request=self.request)
def save(self, **kwargs):
isbn = self.validated_data['isbn']
request = kwargs.get("request", {})
request.data = {'isbn': isbn}
post_book(request)
What I found is that I can't import views (in my example post_book) to serializers I guess that is not allowed by Django.
This will execute your API class.
APIClassB.as_view()(self.request)
If you need to change request method
self.request.method = 'POST'
APIClassB.as_view()(self.request)

Django rest framework queryset custom permissions

I would like to set custom permissions with django guardian on my django rest framework views. I've successfuly achieved it for RetrieveModelMixin, but not for ListModelMixin.
I have a permission class looking like this one :
class CustomPerm(permissions.BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated()
def has_object_permission(self, request, view, object):
if request.method == 'GET':
if object.public is True:
return True
if object.user.is_staff is True:
return True
if 'read_object' in get_perms(request.user, object):
return True
return False
if request.method == 'POST':
#...
I also simplified the view here :
#authentication_classes((TokenAuthentication, SessionAuthentication, BasicAuthentication,))
#permission_classes((CustomPerm,))
class ObjectView(ListModelMixin,
RetrieveModelMixin,
viewsets.GenericViewSet):
queryset = myObject.objects.all()
serializer_class = ObjectSerializer
Behaviour I was naïvly expecting : ListModelMixin could filter by itself objects according to CustomPerm has_object_permission rules.
But it does not work like that. I'm able to do what I want by writing a get_queryset method and applying my custom permission rules, but it seems unappropriate and awful.
Is there a better way ? Thanks :)
PS: I'm sure I'm missing something and my question is naïve but I can't see what.
I'm afraid that the framework doesn't work that way ... The permissions are there to deny the access (when a condition is met), but not to filter the objects for you. If you need to return specific objects, then you need to filter them in the view (queryset) depending on the current user, if needed.
Well overriding isn't awful depending on how you do it... but it is not the question.
If I understand well what you want to do is to filter your queryset using your custom permission.
What I recommend, to keep your code explicit and simple, override your backend filter like in the doc
But be careful filter_queryset apply on both retrieve and list methods

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.