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

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.

Related

Django rest framework XLSX renderer + Apiview

I'm setting up an endpoint on my API which should return a XLSX-file. The DRF-docs mention https://github.com/wharton/drf-renderer-xlsx as the main external rendering package, aside from the pandas which also seem to be able to render XLSX.
In their code example they use a ReadOnlyViewset paired with a mixin, but there is no mention of how it's used with APIViews. Still, I would like to use an APIView as shown by this https://harshahegde.dev/rendering-xlsx-files-in-django-rest-framework-ckagk293p00eumks1bf4dlhie
However..
This works great when using CURL or Postman, but when done through a browser I get this error:
'Request' object has no attribute 'accepted_renderer'
From what I understand this is because there is no Accept header set (e.g 'Accept':'application/xlsx')
I fixed this by removing the Mixin from the renderer_classes, so it simply returns a file called "xlsx" but I can't figure out how to set the filename without the mixin. How do I set the filename using an APIView trying to access the URL from a browser?
My view:
class SomedataXlsx(APIView):
renderer_classes = [XLSXRenderer, JSONRenderer]
def get(self, request):
queryset = Somedata.objects.all()
serializer = SomeDataSerializer(queryset, many=True)
return Response(serializer.data)
Looking at the mixin code it became clear they change the content-disposition header, and so since the DRF Response() takes a header argument I tried changing it, and it worked perfectly.
class SomedataXlsx(APIView):
renderer_classes = [XLSXRenderer, JSONRenderer]
def get(self, request):
user_sub_fund_data = Somedata.objects.all()
serializer = SomeDataSerializer(queryset, many=True)
return Response(serializer.data, headers={"content-disposition":"attachment; filename=mynewfilename.xlsx"})

DRF how to call an api without the the queruset

I am trying to create a rest API in DRF without any DB. I want the user to hit the API with the post data. Once the system receives the data I will do another API call and show the response. My code is:
serializers.py
class getCPHSerializers(serializers.Serializer):
cph_id = serializers.CharField(max_length=128, write_only=True, required=True)
views.py
class GETCPHDetails(generics.ListAPIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
cphID = request.data.get('cphID',None)
errorList = []
if not cphID:
errorList.append({"message": "No CPH Found"})
if len(errorList) == 0:
param = {"searchQry": cphID}
apiResponse = requests.post("http://172.61.25.40:8000/api/newSearch", data=param )
return Response({"message":json.loads(apiResponse)})
I am getting an error
Expected a Response, HttpResponse or HttpStreamingResponse to be returned from the view, but received a <class 'NoneType'>
Any suggestion will be of great help. Thanks in advance.
You are seeing this error because you are not returning a response when
len(errorList) == 0 is False
Python will always return None if you don't return anything
from a method.

Change standard endpoints urls Django Rest Framewrok

I'm using viewsets.ModelViewSet and want to replace the standard endpoints URLs
for example:
instead of creating new snippet with the "standard" endpoint
POST {BAST_URL}/snippet/
I want to replace it with "create" URL and disabling the standard
POST {BAST_URL}/snippet/create/
I able to create a new custom create method but not to
* use "create" in the URL -> ERROR: Cannot use the #action decorator on the following methods, as they are existing routes: create
* Disabling Standart URL from creating a snippet
#action(detail=False, methods=['post'])
def create_snippet(self, request, *args, **kwargs):
return super(SnippettViewSet, self).create(request, *args, **kwargs)
you need to pass an extra argument url_path to the #action decorator like below
#action(detail=False, methods=['post'], url_path='snippet/create', url_name='snippet_create')
def snippet(self, request, *args, **kwargs):
return super(SnippettViewSet, self).create(request, *args, **kwargs)

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():
......

How do I check that user already authenticated from tastypie?

When user authenticates in Django, how do I check that from tastypie?
Once user logs on, the view includes some JS that pulls data from API, which is backed by tastypie.
I have basic authentication/djangoauthorisation set up on my resources, so the browser pops up http auth window. Is there any way to avoid this?
My idea so far is to extend BasicAuthentication so that it first checks session data and when it doesn't find it, it falls back to http auth? AFAIK AJAX calls include session cookies, so this in theory should work? Has anybody done something similar?
I have this solution so far:
class MyBasicAuthentication(BasicAuthentication):
def __init__(self, *args, **kwargs):
super(MyBasicAuthentication, self).__init__(*args, **kwargs)
def is_authenticated(self, request, **kwargs):
from django.contrib.sessions.models import Session
if 'sessionid' in request.COOKIES:
s = Session.objects.get(pk=request.COOKIES['sessionid'])
if '_auth_user_id' in s.get_decoded():
u = User.objects.get(id=s.get_decoded()['_auth_user_id'])
request.user = u
return True
return super(MyBasicAuthentication, self).is_authenticated(request, **kwargs)
which seems to do what I want. If user is logged on, then session contains _auth_user_id, if not, the key is missing.
Anyone can think of any problems this approach may cause?
You may want to check out this ticket on tastypie's GitHub:
https://github.com/toastdriven/django-tastypie/issues/197
The author suggests a very clean approach to authenticate the call with both the session and the API key methods.
There goes the snippet:
class ApiKeyPlusWebAuthentication(ApiKeyAuthentication):
def is_authenticated(self, request, **kwargs):
if request.user.is_authenticated():
return True
return super(ApiKeyPlusWebAuthentication, self).is_authenticated(request, **kwargs)
def get_identifier(self, request):
if request.user.is_authenticated():
return request.user.username
else:
return super(ApiKeyPlusWebAuthentication, self).get_identifier(request)
Once the user is logged in through your API, you have a Django user session. If you want to check if the user is still logged in (on page refresh for example). You can do:
from tastypie.resources import Resource
class LoggedInResource(Resource):
class Meta:
pass
def get_list(self, request, **kwargs):
from django.http import HttpResponse
if request.user.is_authenticated():
return HttpResponse(status=200)
else:
return HttpResponse(status=401)
Client check:
$.ajax({
type: "GET",
url: '/api/loggedin/',
success: function(data) {
// logged in
},
error: function() {
// not logged in
}
});
Pulegium
Why not just as simple as the following:
class CommAuthentication(BasicAuthentication):
def __init__(self, *args, **kwargs):
super(CommAuthentication, self).__init__(*args, **kwargs)
def is_authenticated(self, request, **kwargs):
return request.user.is_authenticated()
I just start to learn tastypie. the above code seemed works for me. Any advantage of your solution ?

Resources