create schema of custom response(exception) handler of DRF - django-rest-framework

I'm using django and DRF with custom exception handler class
custom exception handler code like below
from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
from django.template.response import TemplateResponse
from rest_framework.response import Response
from rest_framework.views import exception_handler
class ExceptionMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def get_response(message="", result={}, status=False, status_code=200):
return {
"message": message,
"result": result,
"status": status,
"status_code": status_code,
}
def get_error_message(self, error_dict):
field = next(iter(error_dict))
response = error_dict[next(iter(error_dict))]
...
return response
def handle_exception(self, exc, context):
error_response = exception_handler(exc, context)
if error_response is not None:
...
return error_response
def __call__(self, request):
response = self.get_response(request)
if response.status_code == 500:
response = self.get_response(
message="Internal server error, please try again later",
status_code=response.status_code
)
elif response.status_code == 400:
...
return JsonResponse(data=response, status=response['status_code'])
So all responses like
{
"message": message,
"result": result,
"status": status,
"status_code": status_code
}
But this form does not appear in Swagger. Can I make this form appear through a global setting? I do not want to use the #swagger_auto_schema decorator.

Related

Pytest login view with knox authentication

I am using knox token in authentication so Here's my Signup and Signin view, How to test the signing view with APIRequestFactory
class SignUpAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
token = AuthToken.objects.create(user)
return Response({"user": UserSerializer(user, context=self.get_serializer_context()).data})
class SignInAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response(
{
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1],
}
)
the endpoints
path("auth/register", SignUpAPI.as_view(), name="RegisterView"),
path("auth/login", SignInAPI.as_view(), name="LoginView"),```
Assuming you work with DRF, you could try something like this:
from rest_framework.test import APIClient
from knox.serializers import User
class SignInAPITests(TestCase):
def test_sign_in(self):
self.user = User.objects.create_user(
username='test_user',
password='12345')
self.client = APIClient()
response = self.client.post(auth_url,
data=dict(username="test_user",
password="12345"))
access_token = res.data["token"]
self.assertIsNotNone(access_token)

How to add the serializer sample response with custom response parameter in drfyasg swagger docs?

ViewSet
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
As in the above viewset i've added custom field token added as a custom field manually. But it's not showing in swagger docs.
What I've tried
from rest_framework import status
from drf_yasg import openapi
from authprofile.serializers import UserSerializer
login_response_schema = {
status.HTTP_201_CREATED: openapi.Response(
description="User Created",
examples= {
"application/json":{
"user":UserSerializer,
"token": "<Token>",
}
}
)
}
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
#swagger_auto_schema(operation_description="Login with email and password", \
responses=login_response_schema)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
Doing so gives me error saying:
TypeError: Object of type 'SerializerMetaclass' is not JSON serializable
Code in serializers.py file
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
password = serializers.CharField(write_only=True, required=True)
def validate(self, attrs):
# Get required info for validation
email = attrs['email']
password = attrs['password']
"""
Check that the email is available in the User table
"""
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise serializers.ValidationError({"email": 'Details do not match an active account'})
if not user.check_password(password):
raise serializers.ValidationError({"password": 'Your password is incorrect'})
return attrs
The below will be used to format the response sample shown in swagger
class LoginResponseSerializer200(serializers.Serializer):
user = UserSerializer()
tokens = JWTTokenResponseSerializer()
Code in views.py file
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
#swagger_auto_schema(
request_body=serializers.LoginSerializer,
responses={
200: LoginResponseSerializer200
}
)
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
# Generate user token
_, token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})

django rest framework running on apache2 not returning full response to client

I'm running an api on apache/2.4.29 built using django rest framework 3.10.3 on top of django 2.2.5 running on wsgi, whenever I try to return a json object for a POST request from a client, the POST method inside apiviews.py that describes success does not send the full data. I have tried on using curl, postman and insomina and the response is the same. On the log it shows that the returning Reponse object is fully sent.
This is the apiviews.py code:
from rest_framework import generics, status, viewsets, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from django.shortcuts import get_object_or_404, get_list_or_404
from django.contrib.auth import authenticate
import threading
import datetime
import json
from commons.models import SMSMessages
from commons.serializers import SMSMessagesSerializer
from notification.sender import sender
class SMSView(APIView):
def post(self, request):
sms_messages_serializer = SMSMessagesSerializer(
data={
"sms_number_to": request.data.get("sms_number_to"),
"sms_content": request.data.get("sms_content"),
"sending_user": request.auth.user_id,
}
)
permission_classes = (permissions.IsAuthenticated)
if sms_messages_serializer.is_valid():
data_to_send = {
"number": sms_messages_serializer.validated_data[
"sms_number_to"
],
"msg_text": sms_messages_serializer.validated_data[
"sms_content"
]
}
sms_object = sms_messages_serializer.save()
else:
print("invalid data - {0}\t{1}".format(sms_messages_serializer.errors, datetime.datetime.now()))
data_to_send = None
max_retry = 0
resp = Response()
while max_retry < 3:
max_retry += 1
status_flag, status_response = sender(data_to_send)
if not status_flag:
resp = Response(
data={
"status": "sms not sent"
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
content_type="application/json"
)
else:
# the update method defined in the SMSMessagesSerializer class
# needs an instance to work with.
sms_messages_serializer.update(
sms_object,
{
"delivery_status": True
}
)
resp = Response(
data={
"status": "sms successfully sent"
},
headers=status_response.headers,
status=status_response.status_code,
content_type="application/x-www-form-urlencoded"
)
print(resp.data)
return resp
else:
resp = Response(
data={
"error": "unable to send sms"
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
content_type="application/json"
)
print(resp.data)
return resp
when sms is successfully sent, on the log file it prints out:
[Wed Dec 23 14:19:29.789772 2020] [wsgi:error] [pid 27330] [remote xxx.xxx.xxx.xxx:xxxx] {'status': 'sms successfully sent.'}
but this is not what is delivered to the client, the client application receives:
{"status":"sms successfull
This is the sender module for more clarity -- it uses the python request library:
import requests
import time
import json
from rest_framework.response import Response
from rest_framework import status
base_url = "http://xxx.xxx.xxx.xxx:xxxx/"
def sender(sms_data):
"""
The actual function that accesses the server and sends the sms.
"""
sending_url = base_url + "api/sendsms/"
sending_headers = {"content-type": "application/x-www-form-urlencoded"}
response = requests.Response()
try:
response = requests.post(
sending_url,
data=sms_data,
headers=sending_headers,
timeout=(7, 14),
)
response.raise_for_status()
except Exception as e:
print("{}".format(e))
return False, response
else:
return True, response
with all the headers intact and this only occurs on the success response, it does not appear on the rest.
How do I make it send the full response to the client?
thank you
The headers set on successful response inform the wrong Content-Length and Content-Type.
Your response is currently built like so:
resp = Response(
data={
"status": "success"
},
headers=status_response.headers,
status=status_response.status_code,
content_type="application/form-data"
)
There you're forwarding headers of the response from the SMS service such as Content-Length, whereas the transferred content is {"status": "success"}.
When Content-Length is not provided in the built response, the framework computes it based on the transferred content.
One more thing to note is that the transferred content is JSON data and not form-data.
I recommend explicitly selecting which headers to send back to the requestor if you care about any specific headers in the SMS service response like so:
resp = Response(
data={
"status": "success"
},
headers={
"specific_header_name_passed_from_sms_service": status_response.headers["specific_header_name_passed_from_sms_service"],
#...
},
status=status_response.status_code,
content_type="application/json"
)
Otherwise if you don't care for any of the headers in the response from the SMS service, you can ignore passing headers option in the built response.

Django Rest Framework return response of library requests from View

I have the following 2 views and on a specific param I need to send a PATCH request to another view on receiving certain params. However i get the following error, how to rectify this?
Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view,
but received a `<class 'requests.models.Response'>`
The view are as follows:
class Emp_Status_Trans_ListView(APIView):
permission_classes = [DjangoCustomModelPermissions]
queryset = Emp_Status_Trans.objects.none()
def get(self, request, format=None):
emp_mast_id=request.query_params.get('employee',None)
linked_model_data = [("hr","Emp_Status_Mast","Emp_Status_Mast_Serializer",("emp_status_mast_id","emp_status_short"))]
final_resp = {}
db_data = Emp_Status_Trans.objects.all().prefetch_related("emp_mast","emp_status_mast")
if emp_mast_id:
db_data = db_data.filter(emp_mast=emp_mast_id)
serializer = Emp_Status_Trans_Serializer(db_data, many=True)
final_resp['emp_status_trans'] = serializer.data
get_linked_data(final_resp, linked_model_data)
return Response(final_resp)
def post(self, request, format=None):
patch_emp_mast=request.query_params.get('patch_emp_mast',None)
serializer = Emp_Status_Trans_Serializer(data=request.data)
if serializer.is_valid():
serializer.save()
if patch_emp_mast:
return self.patch_emp_mast(serializer.data,request.META.get("HTTP_HOST"),request.META.get("HTTP_AUTHORIZATION"))
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch_emp_mast(self,data,domain,access_token):
url = "http://"+domain+"/hr/emp_mast/"+str(data['emp_mast']['id'])+"/"
headers = {'Content-Type': 'application/json', 'Authorization':access_token}
data = {
'emp_status_mast': data['emp_status_mast'],
}
return requests.patch(url,headers=headers, data=json.dumps(data))
patch_emp_mast is called when query param is received however it fails with the error mentioned earlier. How to rectify this?
you can rewrite your code like ;
def patch_emp_mast(self,data,domain,access_token):
url = "http://"+domain+"/hr/emp_mast/"+str(data['emp_mast']['id'])+"/"
headers = {'Content-Type': 'application/json', 'Authorization':access_token}
data = {
'emp_status_mast': data['emp_status_mast'],
}
try:
response= requests.patch(url,headers=headers, data=json.dumps(data))
return Response("status":True,"response":response.json())
expect:
return Response("status":False,"response":{})
Best regards

Django Rest Framework Error HTTP 405 Method Not Allowed method GET

I try to GET data from model but it show error:
Category List
GET /catalog/
HTTP 405 Method Not Allowed
Allow: OPTIONS
Content-Type: application/json
Vary: Accept
{
"detail": "Method \"GET\" not allowed."
}
views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import OcCategory
from .serializers import OcCategorySerializer
class CategoryListView(APIView):
def get_category_list(self, request):
category_list = OcCategory.objects.filter(parent_id=0)
serializer = OcCategorySerializer(category_list, many=True)
return Response(serializer.data)
serializers.py
from rest_framework import serializers
from .models import OcCategory
class OcCategorySerializer(serializers.ModelSerializer):
class Meta:
model = OcCategory
fields = ('category_id', 'image', 'parent_id', 'top', 'column', 'sort_order', 'status', 'date_added', 'date_modified','tiu_id','place')
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.CategoryListView.as_view()),
]
What's wrong?
For APIView class incoming request is dispatched to an appropriate handler method such as .get() or .post().
class CategoryListView(APIView):
def get(self, request, format=None):
pass
def post(self, request, format=None):
pass

Resources