django graphene rate limit (throttling) - throttling

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

Related

Multiple get methods in same class in Django views

I have multiple get methods in the same class. How do I create the urls?
Views.py
class DashboardData(viewsets.ViewSet):
#action(detail=True, methods=['get'])
def get_total(self, request):
Total = File.objects.all().count()
return Response(Total, status=status.HTTP_200_OK)
def get_done(self, request):
Done = File.objects.filter(entry=True).count()
return Response(Done, status=status.HTTP_200_OK)
def get_not_done(self, request):
NotDone = File.objects.filter(entry=False).count()
return Response(NotDone, status=status.HTTP_200_OK)
def get_pending(self, request):
Pending = File.objects.filter(entry=False).count()
return Response(Pending, status=status.HTTP_200_OK)
For example:
http://baser_url/dashboard/total_count should call the get_total() method
http://baser_url/dashboard/done_count should call the done_count() method.
If you want to route view functions more explicitly, then other way of doing so is:
urlpatterns = [
path('dashboard/total_count/', DashboardData.as_view({'get': 'get_total'})),
...,
path('dashboard/done_count/', DashboardData.as_view({'get': 'done_count'})),
]
You can register the methods as custom actions and set the url_path parameter.
Example:
class DashboardData(viewsets.ViewSet):
#action(methods=['get'], detail=False, url_path='total_count')
def get_total(self, request):
Total = File.objects.all().count()
return Response(Total, status=status.HTTP_200_OK)
#action(methods=['get'], detail=False, url_path='done_count')
def get_done(self, request):
Done = File.objects.filter(entry=True).count()
return Response(Done, status=status.HTTP_200_OK)
...
urls.py
router = SimpleRouter()
router.register('dashboard', views.DashboardData, basename='dashboarddata')
You can use the #action decorator, but with detail=False to add the url to the "root" of the viewset.
# views.py
class DashboardViewSet(viewsets.ViewSet):
#action(detail=False, url_path="download")
def get_download(self, request):
pass
# urls.py in your app
router = SimpleRouter()
router.register("dashboard", DashboardViewSet)
urlpatterns = router.urls
From your example it seems like you might be just fine making one single APIView for each function, and then adding them manually with path
class DashPendingView(APIView):
def get(self, request):
return Response(pending_things)
url_patterns = [
path("dashboard/pending/", DashPendingView.as_view(), "dash-pending")
]

reverse() in a test only returns relative URL in a test for django-rest-framework and that causes a 404

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.

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.

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 requests with ajax

I working on a simple mvc application where I have to submit forms with ajax. Then I create a class with purpose to manage all requests from specific module
class Produto(View):
template_name = 'produto/index.html'
def get(self, request):
context = {
'modulos': CELLO_APPS,
'modulo': 'Produtos'
}
return render(request, self.template_name, context)
def post(self, request):
if request.is_ajax() and request.method == "POST":
return JsonResponse({'FOO': 'BAR'})
else:
raise Http404
this looks pretty logical to me, but it doesn't work and raise an error
missing 1 required positional argument: 'request'
the only way I could solve this problem was setting post method as static
class Produto(View):
template_name = 'produto/index.html'
def get(self, request):
context = {
'modulos': CELLO_APPS,
'modulo': 'Produtos'
}
return render(request, self.template_name, context)
def post(request):
if request.is_ajax() and request.method == "POST":
return JsonResponse({'FOO': 'BAR'})
else:
raise Http404
So I have two doubts:
1- How can I create a single class with many functions accessible by ajax?
2- Would be this the best or recommended way to manage those views? (Considering this application can grow a lot in the future)
Edit.
Here follows my urls.py
urlpatterns = [
url(r'^inserir', Produto.inserir, name='produto_inserir'),
url(r'^$', Produto.as_view(), name='cliente'),
]

Resources