DRF detail route without a PK - django-rest-framework

I’d like to create a detail route that renders the data of the current user.
If I use a custom router:
class CustomRouter(routers.DefaultRouter):
def __init__(self, *args, **kwargs):
super(CustomRouter, self).__init__(*args, **kwargs)
self.routes.append(
routers.Route(url=r'^{prefix}/current{trailing_slash}',
mapping={'get': 'current'},
name='{basename}-current',
initkwargs={'suffix': 'Detail'}))
rest_router = CustomRouter()
urlpatterns = [
url(r'^', include(rest_router.urls)),
]
it works, and I can access my logged in user at /api/users/current/.
However, if I use my CustomUser and register the my whole UserViewSet (which descends from viewsets.ReadOnlyModelViewSet):
rest_router.register(r'users', viewsets.UserViewSet)
my current/ route stops working. Is there a way I can add such a custom detail route that doesn’t require a pk?
Edit: my second approach was to add a #list_route that actually returns only one object instead of one. This doesn’t require a custom router at all, just an extra method on my UserViewSet:
class UserViewSet(viewsets.ReadOnlyModelViewSet):
…
#list_route(methods=['get'])
def current(self, request):
return Response(self.serializer_class(request.user).data)
This, however, is pretty ugly for obvious reasons: why would I return one object in a list route?

Related

How can I disable authentication for GET method in django modelviewset?

I have written one router url and modelviewset like below:
**router.register(r'TestViewSet', views.TestViewSet)
**class TestViewSet(viewsets.ModelViewSet): queryset = Test.objects.all() serializer_class = TestSerializer
for this class, I want to disable authentication for GET method.
Pls give some solution
Well to disable authentication only on the GET method of the ModelViewSet, we are required to override the permission class based on the HTTP method.
To achieve this, try following
from rest_framework import permissions
...
class TestViewSet(viewsets.ModelViewSet):
...
def get_permissions(self):
"""Returns the permission based on the type of action"""
if self.action == "list":
return [permissions.AllowAny()]
return [permissions.IsAuthenticated()]
In the above code, we are checking of the action (assuming you want to allow anyone to see the list, and limit who can perform other actions).
So if the action is list (HTTP method GET) it will allow anyone to access otherwise it will check for authentication.
Hope this answers your question.

Openapi - duplicate operationID and extra parameter

I'm documenting my Django API using Swagger.When I generate my Openapi schema, a second route with an additional parameter {format} is added for each of my paths, with the same operation ID as the "correct" route.
I read that the generator may generate duplicate operationId if I have several views with the same model, but this is not my case I think.
Overall, my views (resource/api/views.py) are all organized like this:
class ResourceList(APIView):
"""
View to list Resources.
"""
def get(self, request, parameter1):
...
class ResourceDetail(APIView):
"""
View to retrieve information of a Resource.
"""
def get(self, request, parameter1, parameter2):
...
For these two views, I have these two paths:
urlpatterns = ([
path('<str:parameter1>', views.ResourceList.as_view()),
path('<str:parameter1>/details/<str:parameter2>', views.ResourceDetail.as_view())
])
And the schema generator generates two routes for each.
For the first path:
Route: /api/resource/{parameter1}, Method: get
Route: /api/resource/{parameter1}{format}, Method: get
For the second path:
Route: /api/resource/{parameter1}/details/{parameter2}, Method: get
Route: /api/resource/{parameter1}/details/{parameter2}{format}, Method: get
A warning like this appears:
Route: /api/resource/{parameter1}, Method: get
Route: /api/resource/{parameter1}{format}, Method: get
An operationId has to be unique across your schema. Your schema may not work in other tools.
The warning obviously makes sense, because both routes have the same operationID,(retrieveResourceList in this case). What I don't understand is why the second route is being generated and where that format parameter comes from.
Is this a normal behaviour? If not, what am I doing wrong?
I'm using Autoschema to overide operation_id
from rest_framework.schemas.openapi import AutoSchema
class CandidateAssignToMeList(CandidateMixins,generics.ListAPIView):
schema = AutoSchema(
tags=['LISTVIEW'],
component_name='Assign To Me',
operation_id_base='CandidateAssignToMeList',
)
class CandidateList(CandidateMixins,generics.ListAPIView):
schema = AutoSchema(
tags=['LISTVIEW'],
component_name='Candidate ListView',
operation_id_base='CandidateList',
)
This might not be the way to resolve this fully but in my case, I try commenting the format_suffix_patterns and the format parameter disappear. Here is how I manage the urls:
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [...]
urlpatterns = format_suffix_patterns(urlpatterns) # Comment this

Is there a way to use inheritance in django viewsets or do I have to rewrite the functions?

I am writting an API in Django Rest Framework. I am using viewsets however regular methods create() and update() do not do it for me and I have to rewrite them.
Suppose that I need to do just one check to test if creation of an instance is legit, do I still have to rewrite the whole create function?
Currently I have this:
class LocationViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LocationSerializer
def create(self, request, *args, **kwargs):
user = request.user
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if user.can_create_location():
serializer.save()
return Response(serializer.data)
else:
raise exceptions.PermissionDenied('You dont have permission to create this.')
Instead of rewritting the whole thing, is it possible to somehow use inheritance and do something like this?
class LocationViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LocationSerializer
def create(self, request, *args, **kwargs):
user = request.user
if user.can_create_location():
return super().create(self, request, *args, **kwargs)
else:
raise exceptions.PermissionDenied('You dont have permission to create this.')
The above does not work as I get this error. I feel like this should be possible to do, but I don't know how. Thanks for your help!
AttributeError at /api/0/location
LocationViewSet' object has no attribute 'data'
Request Method: POST
Request URL: http://127.0.0.1:8000/api/0/location
Django Version: 2.1.7
Yes, it does work, and you're doing it almost correctly... as per your error: the function is trying to access LocationViewSet.data as you're passing self in the first argument, change that call to:
super().create(request, *args, **kwargs)
Python standard docs has some info on super() and a link to a more in depth explanation.

Overriding generic.ListView methods for AJAX requests DJANGO

I recently started using django's inbuilt generic views (Create, Update, etc) So I'm updating most of my old views to use them, one of them is the ListView, with pagination. So now, it works right,when i GET that page, it displays the objects as directed, and the pagination works fine. But i want to use AJAX on the pagination so that i just click a "More" button and it gets the next page's objects via ajax and are appended onto the end of the . So i've modified some generic views before to incorporate AJAX like the:
class Delete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
login_url = LOGIN_URL
model = Items
success_url = reverse_lazy('web:member-area')
def test_func(self):
return not self.request.user.is_superuser and self.get_object().created_by == self.request.user
def delete(self, request, *args, **kwargs):
response = super().delete(request)
if self.request.is_ajax():
return JsonResponse({'success': 1}, status=200)
else:
return response
In the above snippet you can see i included the part where it returns something different if the request is AJAX
The current View that i'm working on is as follows:
class Items(ListView):
model = Items
paginate_by = 5
context_object_name = 'items'
template_name = 'web/items/index.html'
which works fine on normal GET requests, so the problem is i dont know which super() method(s) to override and return a different response if its AJAX on that ListView
Use dispatch
class Items(ListView):
def dispatch(request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if request.is_ajax():
return JsonResponse({'success': 1}, status=200)
else:
return response

Django - Custom decorator to allow only ajax request

I have few views in my project which are called only by ajax and I need to put in a check that the views are processed only if the request is ajax. So I thought of writing a decorator. Upon searching for similar type of decorators I hit upon few but couldn't understand anything.
I make use of class based views and will use this decorator on get/post methods. I did find few snippets but all were written for function based views which made it more difficult for me to understand as I have never worked upon function based views.
I just need to know what is idea behind decorators and how it works.
from functools import wraps
from django.core.exceptions import PermissionDenied
def require_ajax(view):
#wraps(view)
def _wrapped_view(request, *args, **kwargs):
if request.is_ajax():
return view(request, *args, **kwargs)
else:
raise PermissionDenied()
return _wrapped_view
After a google search I've found this:
from django.http import HttpResponseBadRequest
def ajax_required(f):
"""
AJAX request required decorator
use it in your views:
#ajax_required
def my_view(request):
....
"""
def wrap(request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest()
return f(request, *args, **kwargs)
wrap.__doc__=f.__doc__
wrap.__name__=f.__name__
return wrap
Didn't tried it, so you have to try it. The essential part is request.is_ajax() which checks if the request is made through AJAX. Check also the docs for more info on is_ajax() method.
EDIT
To decorate a view class in django see Decorating the class in the documentation. Basically the decorator function wraps a method of the class. So you can use the django #method_decorator() to wrap a method in your decorator function (ajax_required):
#method_decorator(ajax_required)
def method_you_want_to_get_only_AJAX_requests():
......

Resources