I want to overwrite Response class of django rest framework so that response back responsive dictionary contain three parameter message, status and data
Hello dears all
I try to change Response Class in DRF to pass two extra parameter ( message, status ) plus data provide by DRF serializer. message pass the message like Done, User Created or etc and status pass the message like fail or success or etc message and this message useful for reserve special code between frontend and backend.
I want to if don't set this parameter return empty character or null result back to client side
for example in success mode:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': [
'value', 'value', 'value'
],
},
}
'message': 'Done',
'status': 'success',
}
and in failure mode:
{
'data': ['any error message raise by serializer',]
'message': 'Create User Failed',
'status': 'failure',
}
I search about my question and found this solution:
if i inheritance DRF Response Class in my class and overwrite __init__ method and get message, data and status in this method and call init of parent with own data structure and use this responsive class in my functionality like this implement:
from rest_framework.response import Response
class Response(Response):
def __init__(self, data=None, message=None, data_status=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
data_content = {
'status': data_status,
'data': data,
'message': message,
}
super(Response, self).__init__(
data=data_content,
status=status,
template_name=template_name,
headers=headers,
exception=exception,
content_type=content_type
)
in success mode call:
return Response(data=serializer.data, message='Done', data_status='success', status=200)
in failure mode call:
return Response(data=serializer.errors, message='Create User Failed', data_status='failure', status=400)
and use own Response class in all views class
we had problem in this solution: if we use GenericViews Class must be overwrite all http methods we used in view's logic and call own class and this is DRY!!
and other solution i found. in serialized layer, we have abstract method def to_representation(self, instance): in Serializer class and implement in other class like ModelSerializer class inheritance Serializer and if we overwrite this method in our serializer class and re fetch data before send to view layer, implement like:
from collections import OrderedDict
class OurSerializer(serializer.ModelSerializer):
....
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['message'] = 'Done'
result['status'] = 'sucssed'
return result
this solution solve above problem but we have two problem again
one: if we use nested serializer and we had overwrite this function in serializer class return bad data like:
{
'data': {
'value_one': 'some data',
'value_two': 'some data',
'value_three': {
'data': [
'value', 'value', 'value'
],
'message': 'Done',
'status': 'sucssed',
},
}
'message': 'Done',
'status': 'sucssed',
}
and message and status repeated and structure not pretty for client
and two: we cant handle exception in this mode and just way to handle exception just with middleware class like this DRF Exception Handling and this isn't useful way, we can't handle any type of error occurs in view and generate comfortable separate message and status.
IF there's another good solution to this question, please guide me.
thanks :)
To resolve this, best practice (that DRF has proposed) is to use 'renderer' classes. A renderer manipulates and returns structured response.
Django uses renderers like Template Renderer and DRF benefits this feature and provides API Renderers.
To do so, you could provide such this renderer in a package (e.g. app_name.renderers.ApiRenderer):
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_dict = {
'status': 'failure',
'data': {},
'message': '',
}
if data.get('data'):
response_dict['data'] = data.get('data')
if data.get('status'):
response_dict['status'] = data.get('status')
if data.get('message'):
response_dict['message'] = data.get('message')
data = response_dict
return json.dumps(data)
And then in your settings file:
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer',
),
...
}
By this action all views that extend DRF generic views will use renderer. If you needed to override setting you can use renderer_classes attribute for generic view classes and #renderer_classes decorator for api view functions.
A comprehensive renderer class to override is available at <virtualenv_dir>/lib/python3.6/site-packages/rest_framework/renderers.py.
This would be more a more robust solution, as it can be used with Generic Views hassle free.
In case of Generic views, the data argument we receive in the render() method is sent automatically by the generic view itself (if not overriding the methods, which will be against DRY), so we cannot handle it, as it does in the accepted answer.
Also, the checks in render() can be easily altered as per the needs (Eg., handling no-2XX status codes in this solution).
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
status_code = renderer_context['response'].status_code
response = {
"status": "success",
"code": status_code,
"data": data,
"message": None
}
if not str(status_code).startswith('2'):
response["status"] = "error"
response["data"] = None
try:
response["message"] = data["detail"]
except KeyError:
response["data"] = data
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
Just an addition: I prefer to inherit from JSONRenderer. That way you get the nice formatting and indentation out of the box
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response = {
'error': False,
'message': 'Success',
'data': data
}
return super(CustomRenderer, self).render(response, accepted_media_type, renderer_context)
Then in your views:
from rest_framework.renderers import BrowsableAPIRenderer
from api.renderers import CustomRenderer
class MyViewSet(viewsets.ModelViewSet):
renderer_classes = [CustomRenderer, BrowsableAPIRenderer]
...
When used with the BrowsableAPIRenderer as shown above, you get your nicely formatted custom response rendered in DRF's Browsable API
Did you try to write custom Response middleware:
class ResponseCustomMiddleware(MiddlewareMixin):
def __init__(self, *args, **kwargs):
super(ResponseCustomMiddleware, self).__init__(*args, **kwargs)
def process_template_response(self, request, response):
if not response.is_rendered and isinstance(response, Response):
if isinstance(response.data, dict):
message = response.data.get('message', 'Some error occurred')
if 'data' not in response.data:
response.data = {'data': response.data}
response.data.setdefault('message', message)
# you can add you logic for checking in status code is 2** or 4**.
data_status = 'unknown'
if response.status_code // 100 == 2:
data_status = 'success'
elif response.status_code // 100 == 4:
data_status = 'failure'
response.data.setdefault('data_status', data_status)
return response
Add middleware in settings:
MIDDLEWARE = [
# you all middleware here,
'common.middleware.ResponseCustomMiddleware',
]
So you can return Response like this:
data = {'var1': 1, 'var2': 2}
return Response({'data': data, 'message': 'This is my message'}, status=status.HTTP_201_CREATED)
Response will be like:
{
"data": [
{
"var1": 1,
"var2": 2
}
],
"message": "This is my message",
"data_status": "success"
}
This is how I solve the problem. I hope it helps
def custom_response(data, code=None, message=None):
if not code and not message:
code = SUCCESSFUL_CODE
message = SUCCESSFUL_MESSAGE
return Response(OrderedDict([
('code', code),
('message', message),
('results', data)
]))
Now in your views function. You can custom the response however you want pretty easy return custom_response(data=..,message=...,code=...)
Related
I'm trying to apply this fix on my django rest framework
Adding root element to json response (django-rest-framework)
But I'm not sure how to override the json serializer on django rest framework, any help would be great.
The end result would be to have the root node name on the Json, because right now it's just an array of objects without the root name i.e.
not it looks like this
[{"foo":"bar"}]
I would need it to be like this
{"element": [{"foo":"bar"}]}
to get it to work with Ember JS
Thanks
I think you have your answer there in the post you've given.
You need to define custom JSON renderer
from rest_framework.renderers import JSONRenderer
class EmberJSONRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
data = {'element': data}
return super(EmberJSONRenderer, self).render(data, accepted_media_type, renderer_context)
and use it as default renderer either in settings or as an explicitly defined render for you view, like:
class MyView(APIView):
renderer_classes = (EmberJSONRenderer, )
# ...
def finalize_response(self, request, response, *args, **kwargs):
response_code = response.status_code
resp = Response(data=response.data, status=response_code)
resp.accepted_renderer=request.accepted_renderer
resp.accepted_media_type = request.accepted_media_type
resp.renderer_context = self.get_renderer_context()
return resp
def get_renderer_context(self):
"""
Returns a dict that is passed through to Renderer.render(),
as the `renderer_context` keyword argument.
"""
# Note: Additionally 'response' will also be added to the context,
# by the Response object.
return {
'view': self,
'args': getattr(self, 'args', ()),
'kwargs': getattr(self, 'kwargs', {}),
'request': getattr(self, 'request', None)
}
I have a builtins.dict dictionary that looks like this:
request = {"data": {"var": "hello", "content": "jello"}}
I am writing a test for an API endpoint in django. I am calling the post method of that class and sending this request to it, and I am accessing the data in the request using request.data. But of course that won't work, because the request that goes into my post function is actually a django.http.HttpRequest, not a builtins.dict. How do I make it so that my request argument is a HttpRequest and not a dict so my function can consume it right?
Edit:
Okay, so I have a function that looks like this:
class EmailView(APIView):
"""
Serializes POST data for sending emails.
"""
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
"""
Sends an email using SES
:param request: POST request
:return: drf http response
"""
serializer = EmailSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
args = serializer.data
send_mail(args.get("subject"), args.get("message"), "info#mycomp.io", args.get("to"), fail_silently=False)
return Response('Email sent.', status=status.HTTP_200_OK)
I want to test this function. So, I've written something that looks like this:
class EmailTests(APITestCase):
def setup(self):
self.user = UserProfile.objects.create_user(
'testuser', email='testuser#test.com', password='testpass')
self.user.save()
def init_request(self):
request = {"data": {"sender": "info#mycomp.io", "to": ["test#gmail.com", "testt#gmail.com"],
"subject": "Subject", "message": "Hello"}}
return request
def test_incorrect_email(self):
request = self.init_request()
reponse = EmailView.post(EmailView, request)
print(reponse)
But, of course, this isn't working because the request i'm sending through my to my post in the test isn't an actual Request from rest_framework.request. Guess I'm saying I have no idea what the right way to write a test for this is... any ideas?
Edit:
My new tests. The EmailView class has not changed.
class EmailTests(APITestCase):
def setup(self):
self.user = UserProfile.objects.create_user(
'testuser', email='testuser#test.com', password='testpass')
self.user.save()
def init_request(self):
request = {"sender": "info#mycomp.io", "to": ["test#gmail.com", "testt#gmail.com"],
"subject": "Subject", "message": "Hello"}
return request
def test_incorrect_email(self):
request = self.init_request()
factory = APIRequestFactory()
request = factory.post('/v1/send_email/', request)
view = EmailView()
reponse = view.post(request)
print(reponse)
Use the Django REST Framework RequestFactory, eg:
from rest_framework.test import APIRequestFactory
view = EmailView.as_view()
factory = APIRequestFactory()
request = factory.post('/url/to/the/endpoint', {"var": "hello", "content": "jello"})
reponse = view(request)
When I attempt to test my Create/POST route for my Django Rest Framework API I receive a response status code of 401 with the error detail telling me ErrorDetail(string=u'Authentication credentials were not provided.', code=u'not_authenticated'). The weird thing is I Django tells me I'm authenticated when I check is is_authenticated.
Does anyone have an idea what might be causing this? All relevant code provided below.
# test_api.py
def authorise_user_and_test_is_authenticated(self, user_id):
"""
Log in user and test this is successful
"""
user = User.objects.get(pk=user_id)
self.client.login(username=user.username, password=user.password)
authorised_user = auth.get_user(self.client)
return self.assertTrue(user.is_authenticated())
def test_create_project(self):
'''
When given valid parameters a project is created.
'''
user = User.objects.get(username="user_001")
self.authorise_user_and_test_is_authenticated(user.id) # pass of authenication and auth testing to method, when tested with is_authenicated() it returns true.
response = self.client.post('/api/user/{0}/project/create/'.format(user.id),
json.dumps({"model_name": "POSTed Project",
"description": "Project tested by posting",
"shared_users[]": [2]
}),
content_type='application/json')
self.assertEqual(response.status_code, 201)
# views.py
class MyCreateView(generics.GenericAPIView):
pass
serializer_class = FerronPageCreateAndUpdateSerializer
def get_queryset(self):
return User.objects.filter(pk=self.kwargs.get('user'))
def post(self, request, format=None, **kwargs):
# This dictionary is used to ensure that the last_modified_by field is always updated on post to be the current user
print request.data
request_data = {
'user': request.user.id,
'model_name': request.data['model_name'],
'description': request.data['description'],
'last_modified_by': request.user.id,
'shared_users': request.data.getlist('shared_users[]', [])
}
serializer = FerronPageCreateAndUpdateSerializer(data=request_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
),
}
# url matcher
url(r'^user/(?P<user>\d+)/project/create/$', MyCreateView.as_view(), name='create-project')
class FerronPageCreateAndUpdateSerializer(serializers.ModelSerializer):
shared_users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), read_only=False)
description = serializers.CharField(max_length=300, trim_whitespace=True, required=False, allow_blank=True)
class Meta:
model = Project
fields = [
'pk',
'user',
'data',
'model_name',
'description',
'created_at',
'date_modified',
'shared_users',
'last_modified_by'
]
Turn's out the issue was here self.client.login(username=user.username, password=user.password) inside the authorise_user_and_test_is_authenticated(self, user_id) method.
The problem was that I was using the password an instance of a user I had already created. This meant when I gave the argument password=user.password, I was trying to log in using a password that had already been hashed. What I needed to do instead was log in with the original unhashed version of the password e.g. password='openseasame'.
I installed recaptcha (work with django rest framework an angularjs) on my site but i don't now how obtain g-recaptcha-response can anyone show example
$scope.submit = function() {
$http.get('https://www.google.com/recaptcha/api/siteverify').success(function (data) {
$http.post(callbackUrl, $scope.callback).success(function (data) {
$scope.isFormActive = false;
}).error(function (data, status, headers, config) {
alert('Incorrect');
});
}).error(function (data, status, headers, config) {
alert('Incorrect');
});
};
i don't now how set parameters secret key and response for https://www.google.com/recaptcha/api/siteverify
Thanks
you can get g-recaptcha-response by calling "grecaptcha.getResponse()" this in your javascript
source: get_recaptcha_response
If you are working with Django REST framework (DRF) you might want to checkout these packages:
https://github.com/Maximilien-R/django-rest-framework-recaptcha
https://github.com/motius/django-rest-recaptcha
https://github.com/zueve/django-rest-captcha
The first two are implementing a DRF recaptcha serializer field, you can put it into your serializer and it has its own validators making a call to google verify URL. Last one (django-rest-captcha package) is implementing a serializer again with its own validators. All packages are on pypi so you can install them using pip.
Frontend integration according to documentation:
<script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token) {
...
});
});
</script>
Below is an example code for BE using django-rest-framework-recaptcha package.
Serializer:
from rest_framework import serializers
from rest_framework_recaptcha.fields import ReCaptchaField
class ReCaptchaSerializer(serializers.Serializer):
recaptcha = ReCaptchaField()
View:
class VerifyTokenAPI(views.APIView):
allowed_methods = ["POST"]
def post(self, request, *args, **kwargs):
serializer = ReCaptchaSerializer(data=request.data)
if serializer.is_valid():
return Response({'success': True}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Urls:
urlpatterns = [
...
path("/my/api/endpoint/", VerifyTokenAPI.as_view()),
]
Basic test call to BE:
data = {"recaptcha": "token"}
response = client.post("/my/api/endpoint/", data)
assert response.status_code == 200
I need to create an API that takes application/x-www-form-urlencoded data in POST data and returns a HTML content in response.
Now to serialize the urlencoded data I am calling following serializer from resource :
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencode']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencode': 'application/x-www-form-urlencoded',
}
def from_urlencode(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
def to_urlencode(self,content):
pass
To specify the response format I have added this function in the resource :
def determine_format(self, request):
return 'text/html'
Now as I am trying to output the HTML response as below :
data = "<html><h1>Hello</h1></html>"
return HttpResponse(data, content_type='text/html', status=200)
I am getting following error :
Sorry, not implemented yet. Please append "?format=json" to your URL.
Can anybody suggest me what is wrong with this code and how to achieve the given requirement.
I have found the answer though not sure if its the correct way to do in tastypie :
Normally return the bundle response from resource.
Add following method in urlencodeSerializer
def to_html(self,bundle, options):
return prepare_html_from_bundle_data(bundle.data)