I'm currently learning Django Rest Framework and currently stuck on a problem which is kind of impossible for me to figure out.
I'm providing you an example, just to make the picture clearer.
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission
class CheckAuthentication(BasePermission):
def has_permission(self, request, view):
authenticated = request.auth
if not authenticated:
raise PermissionDenied(
{
"exception_data": None
}
)
return True
Ok So, in the above example I want to response to be like the following JSON
{
"exception_data": null
}
but instead of it, I'm getting
{
"exception_data": "None"
}
is there any way I can get the desired JSON ??
You can use json package, for ex:
>>> import json
>>> d = {'t':None}
>>> json.dumps(d)
'{"t": null}'
This can be done via custom exception handling
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
exception_data = str(exc.detail['exception_data'])
response.data['exception_data'] = None if exception_data == "None" else exception_data
return response
and then in your settings.py you have to mention the custom_exception_handler, like the following
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Related
Context
I am developing an API Rest with Django Rest Framework, and some of my endpoints consume an external API Rest to create a model instance, the code works but right now I am trying to test my code but I have very struggled with that. I am a little beginner developing tests and maybe the question is very obvious but, I tried so many things, with the below links but I couldn't achieve it.
Mocking external API for testing with Python
How do I mock a third party library inside a Django Rest Framework endpoint while testing?
https://realpython.com/testing-third-party-apis-with-mocks/
Codes
This is my view
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from app.lnd_client import new_address
from app.models import Wallet
from api.serializers import WalletSerializer
class WalletViewSet(viewsets.ModelViewSet):
queryset = Wallet.objects.all()
serializer_class = WalletSerializer
lookup_field = "address"
#action(methods=["post"], detail=False)
def new_address(self, request):
response = new_address()
if "error" in response.keys():
return Response(
data={"error": response["error"]}, status=response["status_code"]
)
else:
return Response(response)
def create(self, request, *args, **kwargs):
response = self.new_address(self)
if response.status_code >= 400:
return response
serializer = self.get_serializer(data=response.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
This is my client with the call to the external api
import requests
from django.conf import settings
from typing import Dict
endpoint = settings.LND_REST["ENDPOINT"]
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
endpoint = (
"https://" + endpoint if not endpoint.startswith("http") else endpoint
)
endpoint = endpoint
auth = {"Grpc-Metadata-macaroon": settings.LND_REST["MACAROON"]}
cert = settings.LND_REST["CERT"]
def new_address() -> Dict:
try:
r = requests.get(
f"{endpoint}/v1/newaddress", headers=auth, verify=cert
)
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
return {
"error": errh.response.text,
"status_code": errh.response.status_code,
}
except requests.exceptions.RequestException:
return {
"error": f"Unable to connect to {endpoint}",
"status_code": 500,
}
return r.json()
My test is this
from django.urls import reverse
from nose.tools import eq_
from rest_framework.test import APITestCase
from unittest.mock import patch
from rest_framework import status
from .factories import WalletFactory
from app.lnd_client import new_address
class TestWalletListTestCase(APITestCase):
def setUp(self):
WalletFactory.create_batch(5)
self.url = reverse("wallet-list")
self.wallet_data = WalletFactory.generate_address()
def test_get_all_wallets(self):
response = self.client.get(self.url)
eq_(response.status_code, status.HTTP_200_OK)
#This is the problem test
#patch('app.lnd_client.requests.post')
def test_create_wallet(self,mock_method):
mock_method.return_value.ok = True
response = self.client.post(self.url)
eq_(response.status_code, status.HTTP_201_CREATED)
Take a look at httmock. It should do what you're looking for. The example from their docs is:
from httmock import urlmatch, HTTMock
import requests
#urlmatch(netloc=r'(.*\.)?google\.com$')
def google_mock(url, request):
return 'Feeling lucky, punk?'
with HTTMock(google_mock):
r = requests.get('http://google.com/')
print(r.content) # 'Feeling lucky, punk?'
You'll want to use something like:
#urlmatch(path=r"(.*)/newaddress$")
def new_address_url(url, request):
return {...} # stuff
with HTTMock(new_address_url)
# make request to DRF api here.
I have the fields refundable and refundable_price in my model. I need to be sure that there is refundable_price not None in case refundable is True.
Since I want it everywhere, I've overridden SubOffer.clean method:
from django.core.exceptions import ValidationError
def save(self, **kwargs):
self.full_clean()
super().save(**kwargs)
def clean(self):
super().clean()
if self.refundable and self.refundable_price is None:
raise ValidationError("V prípade refundovateľnej ponuky je nutné zadať sumu (je možné zadať aj 0)")
And I use ModelViewSet.
class SubOfferViewSet(ModelViewSet):
serializer_class = SubOfferSerializer
filterset_fields = {
# 'approved_by': ['exact'],
# 'approved_dt': ['gte', 'lte', 'gt', 'lt'],
}
def get_queryset(self):
return SubOffer.objects.all()
The weird thing is that when I send POST to the ViewSet it returns 500 instead of JSON errors if there is an error in Suboffer.clean. Other errors work correctly.
It's the same when I use AJAX and when I use DRF API Viewer.
How is that possible and how to make it work properly?
A clean way that will handle all of your Validation errors (and any other errors you might want) is to have a custom EXCEPTION_HANDLER that converts the Django
ValidationError to a DRF one.
See:
from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.serializers import as_serializer_error
from rest_framework.views import exception_handler as drf_exception_handler
def exception_handler(exc, context):
if isinstance(exc, DjangoValidationError):
exc = DRFValidationError(as_serializer_error(exc))
return drf_exception_handler(exc, context)
"""
In settings:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'path.to_module.drf.exception_handler',
}
"""
Source: https://gist.github.com/twidi/9d55486c36b6a51bdcb05ce3a763e79f
A more in-depth example: https://github.com/HackSoftware/Django-Styleguide-Example/blob/9761c7592af553084e95bb5f8f9407a173aac66f/styleguide_example/api/exception_handlers.py
By defaul DRF handle only APIException (see source). Since you are rising Django'sValidationErrorinstead of DRF'sValidation` error, this handler returns None.
So to fix this, you can use ValidationError from DRF:
from rest_framework.exceptions import ValidationError
or which is better to write your own custom exception_handler::
from rest_framework.views import exception_handler
from django.core.exceptions import ValidationError
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if response is None and isinstance(exc, ValidationError):
return Response(status=400)
return response
I want to push front end data (Form inputs) to the server via Ajax. For this, I created an Ajax post request but I'm very unsteady...
At my first attemps, I constantly receive errors by python
Ajax call:
//Get journey time for the stated address
jQuery.ajax({
type: 'post',
url: 'http://127.0.0.1:8000/termin/get-journey-time/',
data: {
'method': 'get_journey_time',
'mandant_id': 1,
'customer_address': customer_address,
'staff_group': staff_group_id
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Error")
},
timeout: 120000,
});
I've created a view in Python, in which I want to do something (views.py)
class get_journey_time(generics.ListAPIView):
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
print(request)
In my url route file I have this code lines (urls.py)
urlpatterns = [
XXXXXXXXXXXXXXXXXXXXXXXXX,
path('termin/get-journey-time/', views.get_journey_time.as_view()),
XXXXXXXXXXXXXXXXXXXXXXXXX,
XXXXXXXXXXXXXXXXXXXXXXXXX,
]
I got the Error code 500:
Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
Is there a mistake in my approach, did I miss anything or is it completely crap?
Define renderer classes and parser classes in settings.py.
Note: You can define many of these (based on requirements and needs) but here we only need JSON related.
As a reference, you can check my repo's this file https://github.com/hygull/p-host/blob/master/src/pmt_hostel_app/views.py. I have used function based views, just ignore the code inside it and focus on request.data and also check related HTML files.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
In this way you will be able to access the posted data in the form of dictionary which can be obtained using request.data in views.
Finally, return Response from the post() method. By default return type of function/method is None and you are just printing the request.
Check the below mentioned links, it will help you a lot.
https://www.django-rest-framework.org/api-guide/renderers/
https://www.django-rest-framework.org/api-guide/parsers/
In client code, I mean in JavaScript code, define a success callback as well (you have just defined error callback).
Please comment, if you are stuck.
you can do it like this
from rest_framework.response import Response
from rest_framework.views import APIView
class get_journey_time(APIView):
# ListAPIView is used for read-only endpoints
#
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# you can get the posted data by request.data
posted_data = request.data
data = {"test": "test"}
return Response(data)
you can get the posted data and use serializers. you can start learning playing with serializers from here
Example serializer code can be like this
from rest_framework import serializers
class DummySerializer(serializers.Serializer):
name = serializers.CharField()
mobile_number = serializers.CharField(required=False)
and then use it in post method of your get_journey_time class
I'm new to django rest_framework, And tried to create customized error reponse in django!.
Django Rest Framework Exceptions
After going through that, all seems to be pretty straight forward, but the import error raise when i try to add the custom exception.
Settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'project.restapi.utils.custom_exception_handler'
}
ImportError
Exception Value:
Could not import 'project.restapi.utils.custom_exception_handler' for API setting 'EXCEPTION_HANDLER'. AttributeError: module 'project.restapi.utils' has no attribute 'custom_exception_handler'
custom_exception_handler.py
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
model.py
class Users(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
# call the original 'list' to get the original response
response = super(Users, self).retrieve(request, *args, **kwargs)
response.data = {"collection": {"data": response.data},"statusCode":response.status_code,"version":"1.0"}
# customize the response data
if response is not None:
return response
else:
# return response with this custom representation
response.data = {"collection": {"data": response.data},"statusCode":response.status_code,"version":"1.0","error":response.exception}
return response
So on the above model works fine, except when i try to hit the user which is not in the database should raise error - not found, so i tried to customise the not found to be meaningful to my own. that's it
I tried to sort out, but hard to do so!!,
Django Version: 2.1.5
Python - 3.7.0
Since your custom_exception_handler resides in a file named custom_exception_handler.py. You can try changing the EXCEPTION_HANDLER setting to this:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'project.restapi.utils.custom_exception_handler.custom_exception_handler'
}
I am trying to get into Django atomic transactions at first time. I wonder if there is a possibility to use it something like this:
class TaskViewSet(MultiSerializerViewSet):
#transaction.atomic
#action(methods=['PATCH'], detail=True)
def move_task(self, request, pk):
# making queries, trying to update them, roll back if last one fails.
return Response("message: SUCCESS", status=_status.HTTP_200_OK)
I have searched a bit - there is some information about how to use transactions but I did not find any info if it is possible to use them with DRF.
class PayViewSet(ModelViewSet):
#action(methods=['PATCH'], detail=True)
#transaction.atomic
def approval(self, request, *args, **kwargs):
sid = transaction.savepoint()
success = something
if success:
transaction.savepoint_commit(sid)
return success_response('yes')
else:
transaction.savepoint_rollback(sid)
return error_response('no')
savepoint is optionally depending on your situation
If you need to wrap all API calls and DRF actions into transactions - you can use (ATOMIC_REQUEST) Django database settings [from Django 1.8]
DATABASES = {
"default": {
....
"ATOMIC_REQUESTS": True,
If you use a custom exception handler (DRF Issue) you may need to adjust it like below
REST_FRAMEWORK = {
....
"EXCEPTION_HANDLER": "api.exceptions.your_exception_handler",
}
import logging
from django.db import IntegrityError
from rest_framework.response import Response
from rest_framework.views import exception_handler, set_rollback
def your_exception_handler(exc, context):
"""
DRF custom exception handler. This will allow us to add useful information
to any exceptions to help out our frontend devs
"""
# Call REST framework's default exception handler first, to get the standard error response.
response = exception_handler(exc, context)
if not response and isinstance(exc, IntegrityError):
# https://github.com/encode/django-rest-framework/issues/5760
if 'duplicate key value violates unique constraint' in str(exc):
set_rollback()
msg = "Unique constraint violated: {exc}".format(exc=exc)
response = Response({"error": True, "content": msg}, status=400)
if response is None:
set_rollback()
logger.exception(exc)
response = Response({"error": True, "content": str(exc)}, status=500)
return response