Django rest framework custom renderer in testing response - django

I'm trying to make my testing code in my django app. My app has a custom renderer class so the response will look like this:
{
"code": 422,
"message": "Unprocessable Entity",
"data": {
"non_field_errors": [
"Unable to log in with provided credentials."
]
}
}
My test code:
class AuthenticationTestCase(TestCase):
"""Test login and registration"""
def setUp(self):
self.client = APIClient()
def test_login_success(self):
"""Test success login"""
payload = {
'email': 'test#test.com',
'password': 'password',
'is_active': True
}
create_user(**payload)
res = self.client.post(LOGIN_URL, payload)
self.assertEqual(res.data['code'], status.HTTP_200_OK)
The client response only return:
"non_field_errors": [
"Unable to log in with provided credentials."
]
Question is, can I make my testing client response using my custom renderer class as well?
EDIT:
This is my custom renderer class:
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
status_code = renderer_context['response'].status_code
response = {
'code': status_code,
'message': renderer_context['response'].status_text,
'data': data,
}
renderer_context['response'].status_code = 200
return super(CustomRenderer, self) \
.render(response, accepted_media_type, renderer_context)

I found the solution. I need to set response's data in renderer class to my custom response like this:
renderer_context['response'].data = response

Related

500 Internal Server Error trying to fetch data with Django Rest Framework through Javascript

I'm trying to get the post data from the backend using fetch. However i am getting a 500 server error when trying to fetch the data. I have tried using .serialize() in the backend to pass the data. as well as trying to return the jsonResponse by serializing the query object which would give me this error: Object of type User is not JSON serializable. I tried it by sending an object and without an object. Any suggestions?
code models.py
class Post(models.Model):
user = models.ForeignKey(User,on_delete=models.PROTECT, default='',null=True)
message = models.TextField()
date = models.DateTimeField()
likes = models.ManyToManyField(User,blank=True,null=True,related_name="posts")
def serialize(self):
return {
"id": self.id,
"user": self.user,
"likes": [user for user in self.likes.all()],
"message": self.message,
"date": self.date.strftime("%b %d %Y")
}
code urls.py
path("post/<int:id>",views.post, name="post")
code views.py
#csrf_exempt
def post(request,id):
post = Post.objects.get(id=id)
if request.method == "GET":
return JsonResponse({"post":post.serialize()})
code script.js (post.getAttribute('name') is the post id)
posts.forEach(post => {
fetch(`post/${post.getAttribute('name')}`)
.then(response => response.json())
.then(data => {
console.log(data)
})
)}
Because you are trying User model instance to convert JSON. Just change "user": self.user to "user": self.user.email or whatever you want
def serialize(self):
return {
"id": self.id,
"user": self.user.email,
"likes": [user.id for user in self.likes.all()],
"message": self.message,
"date": self.date.strftime("%b %d %Y")
}

How to make Post request by list action for create function in viewsets in DRF

I want to send post request from testing. Its working for postman but it didnot work for my test case. How can I give the data by post request.
Views.py,
class PersonalInfoAPI(viewsets.ViewSet):
permission_classes = [IsOwnerPermission]
def get_object(self, pk):
obj = get_object_or_404(Employee.objects.all(), pk=pk)
self.check_object_permissions(self.request, obj)
return obj
def create(self, request):
personal_info = JSONParser().parse(request)
.....
return ...
test.py
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, {"data": {
"appraisal_master_id": 1,
"personal_info": {
"employee_id": 1,
"experience": "abcd",
"education": "Nonoooo"
}
}
}, format='json')
print(resp1)
self.assertEqual(resp1.status_code, 200)
I have got 400 error. Please, tell anyone how can I pass data im properway,..
I got solution. When I gave request which passes dictionary data. then, I coverted into json its working fine.
url = reverse('employee1-list')
self.client = Client(HTTP_AUTHORIZATION='Token ' + token.key)
resp1 = self.client.post(url, data=data, content_type="application/json")
print(resp1)
self.assertEqual(resp1.status_code, 201)

How i can wrap json response data for djago-rest-framework?

I use djano rest framework. I have model, serializer and view:
class PictureModel(models.Model):
id = models.AutoField(primary_key=True)
url = models.CharField(max_length=256)
class PictureView(mixins.ListModelMixin, generics.GenericAPIView):
serializer_class = PictureSerializer
queryset = PictureModel.objects.all()
def get(self, request):
return self.list(request)
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = PictureModel
fields = '__all__'
get request return json :
[
{
"id": 120525, "url": "https://ddd.com/upload/iblock/579/5797cc881f8790926cee4a8fc790d1bd.JPG"
},
{
"id": 120526, "url": "https://ddd.com/upload/iblock/382/382526e1deee07f60871430bd806aa71.JPG"
}
...
]
But i need wrap this data to
{
"success": 1,
"data": [
{
"id": 120525, "url": "https://ddd.com/upload/iblock/579/5797cc881f8790926cee4a8fc790d1bd.JPG"
},
{
"id": 120526, "url": "https://ddd.com/upload/iblock/382/382526e1deee07f60871430bd806aa71.JPG"
}
...
]
How i can do it? I can use middleware for it but i don't know how i can edit response data. Middleware apply for all requests but i need apply it not for all. May be I can use my renderer but I don't know how.
P.S. Also I use swagger documentation auto generator.
UPDATED 09.08.2021:
I specify. I not like way modify get function because I have many views.
I want next. In view i can throw exception, then catch it and return {'success': 0, 'data': None, 'message': exc.message}. If without exception return {'success': 1, 'data': data}
I can make
def get(self, request):
try:
# code
return Response(data={'success': 1, 'data': data})
except MyException as ex:
return Response(data={'success': 0, 'data': None, 'message': exc.message})
but i don't like it. I try create owned rendere class
class ApiRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
if 'success' not in data:
data = {
'status': 1,
'data': data
}
return super(ApiRenderer, self).render(data=data, accepted_media_type=accepted_media_type,
renderer_context=renderer_context)
It works. But if throw exception where i cat catch it, and replace response data?
I like to think simple. How about:
def json_wrapper(status=True, data):
wrapped = {}
wrapped['status'] = 1 if status else 0
wrapped['data'] = data
return wrapped
Can be more flexible using classes but you get the idea.
First of all, you don't need id as a field in Django, rather it is created automatically for you.
Now, let's solve your problem as the following:
class PictureView(mixins.ListModelMixin, generics.GenericAPIView):
serializer_class = PictureSerializer
queryset = PictureModel.objects.all()
def list(self, request):
response = super().list(request)
return Response({"success": 1 if response.data else 0, "data": response.data})

Django REST framework: how to wrap the response with extra fields .... and supply the current response in data field

So, I have the following:
class ObjectViewSet(
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet
):
"""
REST API endpoints for Objects.
"""
serializer_class = ObjectSerializer
queryset = Object.objects.all()
This returns, say, for a list GET request:
[
{
"uuid": "787573a2-b4f1-40df-9e3a-8555fd873461",
},
{
"uuid": "2ab56449-1be1-47d7-aceb-a9eaefa49665",
}
]
However, how could I slightly alter this response for mixins to be similar to the following:
{
success: true,
message: 'Some Extra Useful Message',
data: [
{
"uuid": "787573a2-b4f1-40df-9e3a-8555fd873461",
},
{
"uuid": "2ab56449-1be1-47d7-aceb-a9eaefa49665",
}
]
}
Is this possible, or should I just write my own custom endpoint Response() and not utilise DRF's mixins capability?
So, essentially, switching the custom:
Response(data, status=None, template_name=None, headers=None, content_type=None)
To:
response = {
'success': true,
'message': 'Some Extra Useful Message',
'data': serializer.data
}
Response(response, status=None, template_name=None, headers=None, content_type=None)
After long research, I found this useful and most appropriate to use. For such use cases one must refer to this documentation. In your case, you can do the following -
Declare a class renderer.py
from rest_framework.renderers import JSONRenderer
from rest_framework.utils import json
class JSONResponseRenderer(JSONRenderer):
# media_type = 'text/plain'
# media_type = 'application/json'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
response_dict = {
'status': 'failure',
'data': data,
'message': '',
}
data = response_dict
return json.dumps(data)
Update your settings.py
REST_FRAMEWORK = {
# Other code
'DEFAULT_RENDERER_CLASSES': (
'<app-name>.renderer.JSONResponseRenderer',
)
}
Update your ViewSet class
class YourViewSet(viewsets.ModelViewSet):
# Other code
renderer_classes = [JSONResponseRenderer]
And you're all set! Also refer to this post more.
You can handle this response format using Middelwares. If based on status code you have a fixed format for a response, then write a middleware.
class ResponseFormatMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
try:
if (not getattr(response, 'error', False)) and (isinstance(response.data, dict) or isinstance(response.data, list)):
response.data = {'success': True, 'message':'some message','data': response.data}
except AttributeError:
pass
return response
Middleware is written in CustomMiddleware module as middleware.py, then add
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'CustomMiddleware.middleware.ResponseFormatMiddleware', # Added this line
]
in settings.py file.

How to generate list of response messages in Django REST Swagger?

I have upgraded Django REST Framework to 3.5.0 yesterday because I need nice schema generation.
I am using Django REST Swagger to document my API but don't know how to list all possible response messages that an API endpoint provides.
It seems that there is automatic generation of success message corresponding to the action my endpoint is performing.
So POST actions generate 201 response code, without any description.
How would I go about adding all the response messages that my endpoint provides and give them some descriptions?
I am using
djangorestframework==3.5.0
django-rest-swagger==2.0.7
Ah, Finally got it.
But! This is hack on hack - and probably drf + drf swagger not support that; Basically the problem is not connected to the drf and drf swagger code, rather the openapi codec, see yourself:
def _get_responses(link):
"""
Returns minimally acceptable responses object based
on action / method type.
"""
template = {'description': ''}
if link.action.lower() == 'post':
return {'201': template}
if link.action.lower() == 'delete':
return {'204': template}
return {'200': template}
The above code can be found at: openapi_codec/encode.py - github
This is not connected in any way with drf or drf swagger - just for each link (eg.: GET /api/v1/test/) create a template with empty description.
Of course there's a possibility to overcome this issue. But as I said - this is hack on hack :) I will share an example with you:
docs_swagger.views.py
from rest_framework import exceptions
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_swagger import renderers
from docs_swagger.schema_generator import CustomSchemaGenerator
def get_swagger_view(title=None, url=None):
"""
Returns schema view which renders Swagger/OpenAPI.
(Replace with DRF get_schema_view shortcut in 3.5)
"""
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
generator = CustomSchemaGenerator(title=title, url=url) # this is altered line
schema = generator.get_schema(request=request)
if not schema:
raise exceptions.ValidationError(
'The schema generator did not return a schema Document'
)
return Response(schema)
return SwaggerSchemaView.as_view()
What I do in the CustomSchemaGenerator is as follows:
docs_swagger.schema_generator.py
import urlparse
import coreapi
from rest_framework.schemas import SchemaGenerator
from openapi_codec import encode
def _custom_get_responses(link):
detail = False
if '{id}' in link.url:
detail = True
return link._responses_docs.get(
'{}_{}'.format(link.action, 'list' if not detail else 'detail'),
link._responses_docs
)
# Very nasty; Monkey patching;
encode._get_responses = _custom_get_responses
class CustomSchemaGenerator(SchemaGenerator):
def get_link(self, path, method, view):
"""
Return a `coreapi.Link` instance for the given endpoint.
"""
fields = self.get_path_fields(path, method, view)
fields += self.get_serializer_fields(path, method, view)
fields += self.get_pagination_fields(path, method, view)
fields += self.get_filter_fields(path, method, view)
if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method, view)
else:
encoding = None
description = self.get_description(path, method, view)
if self.url and path.startswith('/'):
path = path[1:]
# CUSTOM
data_link = coreapi.Link(
url=urlparse.urljoin(self.url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
description=description
)
data_link._responses_docs = self.get_response_docs(path, method, view)
return data_link
def get_response_docs(self, path, method, view):
return view.responses_docs if hasattr(view, 'responses_docs') else {'200': {
'description': 'No response docs definition found.'}
}
And finally:
my_view.py
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
responses_docs = {
'get_list': {
'200': {
'description': 'Return the list of the Test objects.',
'schema': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': {
'type': 'integer'
}
}
}
}
},
'404': {
'description': 'Not found',
'schema': {
'type': 'object',
'properties': {
'message': {
'type': 'string'
}
}
},
'example': {
'message': 'Not found.'
}
}
},
'get_detail': {
'200': {
'description': 'Return single Test object.',
'schema': {
'type': 'object',
'properties': {
'id': {
'type': 'integer'
}
}
}
},
'404': {
'description': 'Not found.',
'schema': {
'type': 'object',
'properties': {
'message': {
'type': 'string'
}
}
},
'example': {
'message': 'Not found.'
}
}
}
}
I consider this more like fun instead of a real solution. The real solution is probably impossible to achieve at the current state. Maybe you should ask the creators of drf swagger - do they have plans to support responses?
Anyway, the swagger UI:
Happy coding :)