I have the following views:
class NotificationAPI(viewsets.ModelViewSet):
authentication_classes = (CustomJWTAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
queryset = Notification.objects.all()
def get_queryset(self):
qs = self.queryset.all()
queryset = qs.filter(recipient=self.request.user.id)
return queryset
class MarkAsReadView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = [CustomJWTAuthentication, SessionAuthentication]
#swagger_auto_schema(manual_parameters=[authorization])
def post(self, request, notification_id):
# mark single notification as read
class MarkAllAsReadView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = [CustomJWTAuthentication, SessionAuthentication]
#swagger_auto_schema(manual_parameters=[authorization])
def post(self, request):
#mark all notifications as read
and here is my urls.py routing:
router = routers.DefaultRouter(trailing_slash=False)
router.register('notifications', NotificationAPI, basename='notifications')
urlpatterns = [
path('', include(router.urls)),
# notifications
path('notifications/mark-as-read/<int:notification_id>',
MarkAsReadView.as_view(), name='mark-as-read'), # this work
path('notifications/mark-all-as-read',
MarkAllAsReadView.as_view(), name='mark-all-as-read'), #this doesn't work
]
Currently whenever i call mark-as-read it work fine, but when i try to call mark-all-as-read api it always return the following 405 error:
{
"detail": "Method \"POST\" not allowed."
}
I checked and it seem like the notifications ModelViewSet routing messing up the other mark-all-as-read ApiView route.
I checked by commenting the ModelViewSet out and the mark-all-as-read now work.
Why is this happening? shouldn't routing be separate by name and basename ? Why it's when i call mark-all-as-read it go into the ModelViewSet route, but the mark-as-read work normally?
UPDATE:
i had update my NotificationAPI modelviewset api with custom route as suggestion from Risadinha but it's displaying serializer in body, how do i remove it? because there no need for body data in my mark-as-read and mark-all-as-read requests
My updated NotificationAPI
class NotificationAPI(mixins.ListModelMixin,
viewsets.GenericViewSet):
authentication_classes = (CustomJWTAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
queryset = Notification.objects.all().order_by('-id')
serializer_class = NotificationSerializer
search_fields = ('verb__unaccent', 'description__unaccent')
pagination_class = ListPagination
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['unread']
def get_queryset(self):
qs = self.queryset.all()
queryset = qs.filter(recipient=self.request.user.id)
return queryset
#action(
basename='mark-all-as-read',
detail=False,
methods=['post'],
name='mark-all-as-read',
url_path=r'mark-all-as-read',
url_name='Mark All As Read'
)
def mark_all_as_read(self, request):
#do some stuffs
#action(
basename='mark-as-read',
detail=False,
methods=['put'],
name='mark-as-read',
url_path=r'mark-as-read/(?P<pk>\d+)'
)
def mark_as_read(self, request, pk=None):
#do some stuffs
Related
I am working on DRF (Django) API
I have a simple ModelViewSet. Delete request is working good for this:
class BuildingObjectViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
serializer_class = BuildingObjectSerializer
queryset = BuildingObject.objects.all()
I want to customize delete function. But this delete code works too long time > 1 minute
What is the reason of too long time of request?
class BuildingObjectViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
serializer_class = BuildingObjectSerializer
queryset = BuildingObject.objects.all()
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
deleted_id = instance.pk
self.perform_destroy(instance)
response = {
"message": "BuildingObject has been deleted.",
"building_object_id": deleted_id
}
return Response(response, status=status.HTTP_204_NO_CONTENT)
models.py
class BuildingObject(models.Model):
name = models.CharField(max_length=32)
address = models.CharField(max_length=200)
urls.py
path(
"building_objects/<int:pk>/",
BuildingObjectViewSet.as_view({
"delete": "destroy",
"get": "retrieve",
"patch": "partial_update",
}),
name="building_objects_detail"
),
Hello developer I'm beginner to django , im following a tutorial in which he create concrete views classes and convert them to viewset.ModelViewSet classes. and he use default router in urls.py
my app showing list of articles with viewset but not perform the post method with the ArticleViewSet(viewset.ModelViewSet)
so im confused about it to use viewset
This is my api/view.py file im using Concrete View Classes in which i using concrete view classses **
class ArticleListView(ListAPIView):
queryset = Articles.objects.all()
serializer_class = ArticleSerializer
class ArticleDetailView(RetrieveAPIView):
queryset = Articles.objects.all()
serializer_class = ArticleSerializer
class ArticleUpdateView(UpdateAPIView):
queryset = Articles.objects.all()
serializer_class = ArticleSerializer
class ArticleDeleteView(DestroyAPIView):
queryset = Articles.objects.all()
serializer_class = ArticleSerializer
class ArticleCreateView(CreateAPIView):
permission_classes = []
#parser_classes = (FormParser,MultiPartParser, FileUploadParser )
serializer_class = ArticleSerializer
queryset = Articles.objects.all()
#serializer = ArticleSerializer(queryset, many=True)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(True, status=200)
# class ArticleViewSet(viewsets.ModelViewSet):
# parser_classes = (FormParser,MultiPartParser, FileUploadParser )
# serializer_class = ArticleSerializer
# queryset = Articles.objects.all()
**this are my urls patterns api/url.py in Article app **
urlpatterns = [
path('articles', ArticleListView.as_view() name=""),
path('xyz', ArticleCreateView.as_view()),
path('<pk>', ArticleDetailView.as_view()),
path('<pk>/update/', ArticleUpdateView.as_view()),
path('<pk>/delete/', ArticleDeleteView.as_view())
]
#from articles.api.views import *
# from rest_framework.routers import DefaultRouter
# router = DefaultRouter()
# router.register(r'', ArticleViewSet, basename='articles')
# urlpatterns = router.urls
**These are my url patterns of urls.py in settings folder **
urlpatterns = [
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('rest-auth/', include('rest_auth.urls')),
path('rest-auth/registration/', include('rest_auth.registration.urls')),
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
path('api/', include('articles.api.urls')),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I'm not sure, I did only a rapid search and your given info was not complete but I think you should define the below functions to give the functionality to your viewSet.
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
or you can write your own viewsets rather than overwriting them
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
fro more accurate details you can see the documentation
I have a small messaging API where the message contains a mark read boolean field.
I'm trying to automatically update the message instance so if the user logged in after the message was created, it'll be marked as read.
class MessagesViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing the messages
associated with the user.
"""
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = MessageSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = FILTERS.FILTER_SET
search_fields = FILTERS.SEARCH_FIELDS
ordering_fields = FILTERS.ORDERING_FIELDS
ordering = [MessageFields.DATE, ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
def perform_create(self, serializer):
"""
Set the sender to the logged in user.
"""
serializer.save(sender=self.get_user())
def perform_update(self, serializer):
"""
Update the message read field to true if necessary.
"""
date = self.kwargs[MessageFields.DATE]
mark_read = self.kwargs[MessageFields.MARK_READ]
last_login = self.get_user().last_login
# If the message hasn't been read yet.
if not mark_read:
if last_login > date:
serializer.save(mark_read=True)
pass
pass
But this is not updating the object when I access it.
The perform_update method will be ran if you send a PUT or PATCH request. What you want to do is to mark messages as True whenever user gets the messages. So you can either override get_queryset or list and retrieve functions.
For example you can try this:
class MessagesViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing the messages
associated with the user.
"""
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = MessageSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = FILTERS.FILTER_SET
search_fields = FILTERS.SEARCH_FIELDS
ordering_fields = FILTERS.ORDERING_FIELDS
ordering = [MessageFields.DATE, ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
def list(self, request):
serializer = MessageSerializer(self.get_queryset(), many=True)
for instance in serializer.data:
instance['mark_read'] = True
serializer.save()
return Response(serializer.data)
And for routing:
urlpatterns += [path('messages/',
MessagesViewSet.as_view({'get': 'list', 'post': 'create'}))]
Also you don't need to override perform_create method, it'll work fine.
I've also come around this issue and what helped me was overriding the update method itself and getting the instance object from there...
For example in your case, add:
def update(self,request,*args,**kwargs):
instance = self.get_object()
instance.sender = self.get_user()
serializer = self.get_serializer(instance,data = request.data)
self.perform_update(serializer)
return Response(serializer.data)
I have a ViewSet which permission_classes is set to (permissions.IsAuthenticated,), but I want this view to allow not authenticated access when the method is retrieve().
This is my ViewSet:
class AlbumViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
queryset = proxies.AlbumProxy.objects.all()
serializer_class = serializers.AlbumSerializer
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter,)
search_fields = ('name', 'description', 'company__name')
filter_fields = ('code', 'company')
def retrieve(self, request, pk):
password = request.query_params.get('password', None)
instance = proxies.AlbumProxy.objects.get(code=pk)
if instance.access_code != password and password != settings.MASTER_KEY:
raise Exception(_("Invalid password for album {}".format(instance.code)))
instance_to_return = serializers.AlbumSerializer(instance=instance, context={'request': request}).data
instance_to_return.pop('access_code')
return Response(instance_to_return)
Is there a way I can disable permission_classes when the method retrieve() is on, but to leave it working in any other case?
You can override get_permissions like so:
def get_permissions(self):
if self.action == 'retrieve':
return [] # This method should return iterable of permissions
return super().get_permissions()
Django Rest Framework gives what you need out of the box. See IsAuthenticatedOrReadOnly permission.
My settings:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 2
}
My pagination Class:
from rest_framework.pagination import PageNumberPagination
class CustomNumberPagination(PageNumberPagination):
page_size = 5
My Testing View Class:
from rest_framework.pagination import PageNumberPagination
from .pagination import CustomNumberPagination
class Testing(generics.GenericAPIView):
queryset = Testing.objects.all()
serializer_class = TestingSerializer
pagination_class = CustomNumberPagination
def get(self, request):
print PageNumberPagination.page_size # 2
print self.pagination_class.page_size # 5
queryset = self.get_queryset()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I can print out the page_size of PageNumberPagination and CustomNumberPagination in my console correctly.
However, passing page as a parameter doesn't have any effect. I couldn't get either global paginations or pagination_class in each view to work. I am not sure what went wrong, but it seems that most people did the same thing and just worked for them. I'd appreciate any suggestions for me.
Updates
Just got some inspirations from my selected answer below.
Since I will have to write a lot of customizations in my overwritten get(), I just updated my get():
from rest_framework.pagination import PageNumberPagination
from .pagination import CustomNumberPagination
class Testing(generics.GenericAPIView):
queryset = Testing.objects.all()
serializer_class = TestingSerializer
pagination_class = CustomNumberPagination
def get(self, request):
queryset = self.get_queryset()
page = self.request.query_params.get('page')
if page is not None:
paginate_queryset = self.paginate_queryset(queryset)
serializer = self.serializer_class(paginate_queryset, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
Take a look how it is done in drf itself:
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Hope that this will help you - as is self-explanatory;
You used GenericAPIView - and overwrite the get - you should use the get_paginated_response method to achieve pagination.
Happy coding.