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.
Related
I am trying to test the endpoints for my API by using this guide. Specifically, this block is supposed to test the get request:
class GetAllPuppiesTest(TestCase):
""" Test module for GET all puppies API """
def setUp(self):
Puppy.objects.create(
name='Casper', age=3, breed='Bull Dog', color='Black')
Puppy.objects.create(
name='Muffin', age=1, breed='Gradane', color='Brown')
Puppy.objects.create(
name='Rambo', age=2, breed='Labrador', color='Black')
Puppy.objects.create(
name='Ricky', age=6, breed='Labrador', color='Brown')
def test_get_all_puppies(self):
# get API response
response = client.get(reverse('get_post_puppies'))
# get data from db
puppies = Puppy.objects.all()
serializer = PuppySerializer(puppies, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
When I try to adapt this to my own test, it looks like this:
from ..models import DemanderFeature, DemanderFeatureCollection
from rest_framework import status
from django.test import TestCase, Client
from django.urls import reverse
from ..serializers import DemanderFeatureCollectionSerializer
class GetAllDemanderFeatureCollections(TestCase):
def setUp(self):
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0')
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1')
def test_get_all_demandercollections(self):
# get API response
response = client.get(reverse('demandercollections-list'))
# get data from db
demanderfeaturecollections = DemanderFeatureCollection.objects.all()
serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
The problem is however the reverse() method is only returning the relative URL, (/demandercollections/) and then client.get(reverse(...)) returns a 404. I don't understand how I can force it to use the actual explicit URL during testing.
I'm using Django 3.
My main urls.py looks like this:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("app.urls")),
path('api-auth/', include('rest_framework.urls')),
]
And my module urls.py looks like this:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from app import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'demanders', views.DemanderFeatureViewSet)
router.register(r'demandercollections', views.DemanderFeatureCollectionViewSet, basename="demandercollections")
router.register(r'producers', views.ProducerFeatureViewSet)
router.register(r'producercollections', views.ProducerFeatureCollectionViewSet)
router.register(r'pathfinderrunconfigurations', views.PathfinderRunConfigurationViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
urlpatterns = [
path('', include(router.urls)),
]
The DemanderCollectionViewSet in views.py looks like this:
class DemanderFeatureCollectionViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
queryset = DemanderFeatureCollection.objects.all()
serializer_class = DemanderFeatureCollectionSerializer
lookup_field = 'name'
#action(detail=True, methods=["get"])
def geojson(self, request, *args, **kwargs):
demanders = DemanderFeature.objects.filter(demandercollection=self.get_object())
return Response(serialize('geojson', demanders, geometry_field='geom', fields=('name',)))
#action(detail=True, methods=["patch"])
def commit(self, request, *args, **kwargs):
demandercollection = self.get_object()
if not request.data["committed"]:
# User is trying to "uncommit", do not allow this
return Response("You may not un-commit a DemanderCollection. You must copy it and make modifications on the copy.", status=status.HTTP_400_BAD_REQUEST)
demandercollection.committed = True
demandercollection.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
return DemanderFeatureCollection.objects.filter(deleted=False).filter(owner=user).order_by("name")
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def destroy(self, request, *args, **kwargs):
demandercollection = self.get_object()
demandercollection.deleted = True
demandercollection.save()
return Response(f"Successfully deleted DemanderCollection.")
POST ANSWER EDIT
not only was the accepted answer indeed the culprit, but it also revealed that the DemanderFeatureCollection objects being created must also be created with an owner attribute, and the client object must call its login() method to a valid user credential pair.
The test class therefore had to be updated to look like this:
class GetAllDemanderFeatureCollections(TestCase):
""" Test module for GET all puppies API """
def setUp(self):
self.test_user = User.objects.create_user('test_user', 'a#b.com', 'test_user')
self.other_user = User.objects.create_user('other_user', 'a#b.com', 'other_user')
client.login(username="test_user", password="test_user")
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection0', owner=self.test_user)
DemanderFeatureCollection.objects.create(name='testdemanderfeaturecollection1', owner=self.test_user)
DemanderFeatureCollection.objects.create(name='otherdemanderfeaturecollection0', owner=self.other_user)
def test_get_all_demandercollections_for_user(self):
# get API response
response = client.get(reverse('demandercollections-list'))
# get data from db
demanderfeaturecollections = DemanderFeatureCollection.objects.filter(owner=self.test_user).all()
serializer = DemanderFeatureCollectionSerializer(demanderfeaturecollections, many=True)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
In get_queryset() method of DemanderFeatureCollectionViewSet class you are filtering the model instances with owner field against the logged-in user.
In your test-cases, you are creating the DemanderFeatureCollection instances without linking the user and hence DRF raising an HTTP 404 error. So, attaching the user to the instance and making the request with the same user will give you a proper response from the API.
I am building a REST Api on Django RF. I need to set a requests limit from IP. It's easy to do that for a regular Api endpoint in views.py just adding following settings
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
But I also have a Graphene django for a graphql api.
How can I set up a rate limit for that view? I have tried django-ratelimit, but it didn't work for me.
The issue was solved by customising GraphQL view to the following:
from graphene_django.views import GraphQLView
class CustomGraphQL(GraphQLView):
def parse_body(self, request):
if isinstance(request, Request):
return request.data
return super().parse_body(request)
#classmethod
def as_view(cls, *args, **kwargs):
view = super().as_view(*args, **kwargs)
view = authentication_classes((TokenAuthentication,))(view)
view = throttle_classes((AnonRateThrottle, AnonMinutesThrottle,
AnonSecondsThrottle, UserRateThrottle))(view)
view = api_view(['GET', 'POST'])(view)
return view
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.
i have this view handling only /GET/ request to display all authors only if the logged user is superuser
class AuthorView(generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.all()
permission_class = (IsSuperUser,)
And the permission for superuser:
class IsSuperUser(permissions.BasePermission):
def has_permission(self, request, view):
user = request.user
return user.is_authenticated() and user.is_superuser
The permission does not seems to be working, placed the pdb in has_permission the control does not seems to coming there.
What am i missing ?
It should be:
permission_classes = (IsSuperUser,)
and not
permission_class = (IsSuperUser,)
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 ?