How to use allow only POST in Django REST API Viewset - django

I went through the django rest framework documentation on Viewsets and dont seem to understand how to allow only POST requests on the browser API.
Viewset
class EmailViewSet(viewsets.ModelViewSet):
queryset = models.Email.objects.all()
serializer_class = serializers.EmailSerializer
Model
class Email(models.Model):
email = models.EmailField(max_length=50,unique=True)
def __str__(self):
return str(self.email)
Serializer
class EmailSerializer(serializers.ModelSerializer):
class Meta:
model = models.Email
fields = ["email"]

Viewsets are used together with a router and, depending on what is being exposed in your viewset, various GET and POST will be created by the Django REST framework automatically.
Your EmailViewSet is a ModelSerializer, and exposes .list() (), .retrieve(), .create(), .update(), .partial_update(), and .destroy(), through inheritance. All those actions are GET and POST either at the {prefix}/ and {prefix}/{url_path}/ of your router.
If you want to narrow the set of actions, you should derive EmailViewSet from specific mixins that are limiting the actions of the viewset, for instance (see this example):
CreateModelMixin will be a POST on {prefix}/
UpdateModelMixin will be a POST on {prefix}/{url_path}/

from . import models, serializers
from rest_framework import mixins
class EmailViewSet(viewsets.GenericViewSet,mixins.CreateModelMixin,):
queryset = models.Email.objects.all()
serializer_class = serializers.EmailSerializer
def create(self,request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)

Related

django rest framework Viewset for single item, not list

I am having a problem with my DRF API. I want to provide a read only endpoint that takes a single id from a object(document) in the DB and runs a script to perform an check and gives back the result.
It works fine when I call the API with //api/documentcheck/1/ where 1 is the pk of the document in the DB.
The problem is that if I just call the base URL //api/documentcheck/ it tries to give back the results of all documents in the database and times out because it takes ages.
I am looking for a way to remove the list view and force users to provide the ID for a single document to check.
This is my serializer class
class DocumentCheckSerializer(serializers.Serializer):
'''Serializer for document check'''
class Meta:
model = Document
fields = '__all__'
def to_representation(self, value):
return process_document(value)
This is my view:
class DocumentCheck(viewsets.ReadOnlyModelViewSet):
"""Check a single document"""
authentication_classes = (
TokenAuthentication,
SessionAuthentication,
BasicAuthentication,
)
permission_classes = [IsAuthenticated]
queryset = Document.objects.all()
serializer_class = serializers.DocumentCheckSerializer
and my router entry
router.register("documentcheck", views.DocumentCheck, basename="documentcheck")
You can use just GenericViewSet and RetrieveModelMixin as your base class
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import RetrieveModelMixin
class DocumentCheck(GenericViewSet, RetrieveModelMixin):
"""Check a single document"""
authentication_classes = (
TokenAuthentication,
SessionAuthentication,
BasicAuthentication,
)
permission_classes = [IsAuthenticated]
queryset = Document.objects.all()
serializer_class = serializers.DocumentCheckSerializer

Django rest framework: how to make a view to delete multiple objects?

I am building a simple Photos-Albums app with Django Rest Framework (DRF). I would like to be able to delete multiple albums at once by supplying an array of ids. I am using viewsets.ModelViewSet for the basic views.
class AlbumViewSet(viewsets.ModelViewSet):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
I have managed to create a view to show the albums before the delete operation by adding this function to my views.py file.
#api_view(['GET', 'DELETE'])
def delete_albums(request):
"""
GET: list all albums
DELETE: delete multiple albums
"""
if request.method == 'GET':
albums = Album.objects.all()
serializer = AlbumSerializer(albums, many=True)
return Response(serializer.data)
elif request.method == 'DELETE':
ids = request.data
albums = Album.objects.filter(id__in=ids)
for album in albums:
album.delete()
serializer = AlbumSerializer(albums, many=True)
return Response(serializer.data)
If I run curl -X delete -H "Content-Type: application/json" http://localhost:8000/albums/delete -d '{"data":[1,2,3]}' then it will delete albums with ids 1,2,3.
This is OK, except that:
It's not class-based, and I'd prefer to have everything class-based if possible
I would prefer to have a form in the view that lets me input an array e.g. [1,2,3], hit a delete button, and then see the results of the query in the browser, much like when one posts a new object through the interface.
Can anyone please outline how to achieve that? Thanks!
You can use the action decorator
from rest_framework.decorators import action
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.request import Request
#import your model and serializer classes
class AlbumViewSet(viewsets.ModelViewSet):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
#action(methods=["DELETE"], details =False, )
def delete(self, request:Request):
delete_id =request.data
delete_albums = self.queryset.filter(id__in=delete_id)
delete_albums.delete()
return Response( self.serializer_class(delete_albums,many=True).data)
assuming your modelViewSet api point was /api/albums
you could now make a delete request to /api/albums/delete
You could check out the full document on viewsets at ViewSet
and on how to use action decorator provided by the django rest framework

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

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

django rest framework cannot POST data

I implemented a basic rest api with the django rest framework. It works perfectly using the browsable api or communicating to it with requests. Next step would be submitting data to the rest api.
Here is what I have done so far.
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny',),
'PAGINATE_BY': 10
}
[UPDATE:]
models.py
class Request(models.Model):
name = models.TextField()
def save(self, *args, **kwargs):
super(Request, self).save(*args, **kwargs) # Call the "real" save() method.
serializers.py
class RequestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Request
fields = ('id', 'name')
views.api
class RequestsViewSet(viewsets.ModelViewSet):
queryset = Request.objects.all()
serializer_class = RequestSerializer
Using the browsable api I see that those are the options supported:
Allow: GET, HEAD, OPTIONS
Obviously, POST (and also PUT) is missing.
What I am doing wrong?
Thanks!
Solved it by adding the post method to the modelviewset (in the view):
def post(self, request, format=None):
...
Thanks for helping!
Well, I think you only need to call save method on the model object to persist the object in the database.
First, import model to the view, instantiate a model object in the view, then call save method on the newly created object. If you have model connected to the backend, that will persist your changes.
models.py
class YourModel(models.Model):
name = models.CharField()
views.py
from models import YourModel
def yourView(request):
yourObject = YourModel(name='John')
yourObject.save()
...
Check also Django documentation for models here