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)
}
Related
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=...)
I want to pass a value through the Headers of a get request.
Im trying the below but it doesn't work,
class ListCategoriesView(generics.ListAPIView):
"""
Provides a get method handler.
"""
serializer_class = CategorySerializer
def get(self, request, *args, **kwargs):
token = request.data.get("token", "")
if not token:
"""
do some action here
"""
if not UserAccess.objects.filter(accessToken=token).exists():
"""
do some action here
"""
else:
"""
do some action here
"""
I want to pass the token in the headers like that :
can anyone help me with this issue,
thanks a lot in advance.
You said it yourself, you're passing it in the headers, so you need to get it from there. DRF does not do anything special to access the headers, so it proxies to the underlying Django HttpRequest object, which makes them available via the META attribute, converted to uppercase and prefixed by HTTP_:
token = request.META.get("HTTP_TOKEN", "")
(using Django2.0)
This is my first time trying to collaborate with a frond-end developer and I am trying to serialize a Django model from a generic ListView. Even though I manage to send a JsonResponse with my objects as json, they are always a string:
"[{\"model\": \"questions.question\", \"pk\": 9535, \"fields\": {\"created\": \"2018-04-14T17:02:38.559Z\", \"modified\": \"2018-04-14T18:04:14.264Z\", \"question\": \"TEST\", \"category\": \"Rules\", \"event\": \"Beyonce\", \"answer\": \"aergaergaergaer\", \"verified\": true, \"verified_by\": [\"someotheruser\"], \"count\": 0, \"user_created\": [\"someuser\"]}}]"
the way the front-end developer solved this issue is by calling a JSON.parse(). (see: https://www.w3schools.com/js/js_json_parse.asp).
Is this the correct way to do it or should I return the objects without a string?
If I am wrong and there is a way to do this without the strings here is my view and url:
views.py:
from events.models import Event
from django.core import serializers
from django.http import JsonResponse
class EventView(LoginRequiredMixin, ListView):
login_url = '/accounts/login/'
model = Question
template_name = 'faq/faq.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
events_list = Event.objects.all()
context['events_list'] = events_list
return context
def get_queryset(self):
event = Event.objects.get(event=self.kwargs['event'])
queryset = Question.objects.filter(event=event)
return queryset
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
data = serializers.serialize("json", queryset, use_natural_foreign_keys=True)
return JsonResponse(data, status=200, safe=False)
urls.py
urlpatterns += [
path('<str:event>/', EventView.as_view(), name='event'),
]
What I also tried:
def EventRequest(request, **kwargs):
event = Event.objects.get(event=kwargs['event'])
queryset = Question.objects.filter(event=event)
data = serializers.serialize("json", queryset, use_natural_foreign_keys=True)
dump = json.dumps(data)
return HttpResponse(dump, content_type='application/json')
and:
def EventRequest(request, **kwargs):
event = Event.objects.get(event=kwargs['event'])
queryset = Question.objects.filter(event=event)
data = serializers.serialize("json", queryset, use_natural_foreign_keys=True)
return JsonResponse(data, status=200, safe=False)
Once again this could be absolutely correct and the front-end developer should just do a JSON.parse(). Please let me know, thanks!
This is normal. JSON is a string notation. It is just a format to represent JavaScript objects and arrays (Python dicts and lists) as a string.
In the frontend you'll have to use JSON.parse() to convert it into a JavaScript array (list) or object (dict).
This also holds true when you send JSON from frontend to backend. You use JSON.stringify() to covert the JS object to string. Then in the backend you convert that string to a Python object using json.loads().
I've created a base api view, which extends from APIView, where I log response time, log request, and other common stuffs.
Now, I also want to add request validation here, using the Serializer defined in sub-class Views. I thought the appropriate place is to put that in dispatch() method. But before I call API.dispatch() method, request.data is not prepared. So, that won't work. Can someone help me in right direction as to how to move validation to a single place?
Here's the class structure:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
# Some code here
# How to use `validation_serializer` here, to validate request data?
# `request.data` is not available here.
response = super(BaseView, self).dispatch(request, *args, **kwargs)
# Some code here
return response
class MyView(BaseView):
validation_serializer = ViewValidationSerializer
def post(self, request, *args, **kwargs):
pass
I thought another approach could be use decorator on the top of post() method. But if only there was an cleaner way, than putting decorators all across the project?
Note: It's similar to the question here: Django - DRF - dispatch method flow. But as per the suggestion there, I don't want to just copy the entire dispatch method from DRF source code.
The method that processes the django request into a DRF request (and adds the request.data property) is the APIView.initialize_request . The APIView.dispatch() method calls it and then proceeds to call the appropriate method handler (post/patch/put).
You can try to do that yourself by calling it and using the returned object:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
kwargs['context'] = self.get_serializer_context()
serializer = self.validation_serializer(data=request.data, *args, **kwargs)
# use `raise_exception=True` to raise a ValidationError
serializer.is_valid(raise_exception=True)
response = super(BaseView, self).dispatch(request, *args, **kwargs)
return response
However, I would suggest against this, as other functionality of dispatch() probably should be performed prior to handling validation; thus, you could move the above logic to the relevant post/patch/put methods instead.
In these methods you can also use self.request directly since it was already initialized by dispatch().
I think drf-tracking does what you are looking for. You may want to check it out.
I don't think you're going about this the correct way. The best way to log the request, with validation is in your Authentication Class and add the audit log to the request.
Then you can use your APIView to log the render time, against the AuditLog generated in the Authentication Class.
Here's an example using Token Authentication, assuming each request has a header Authorization: Bearer <Token>.
settings.py
...
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'common.authentication.MyTokenAuthenticationClass'
),
...,
}
common/authentication.py
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions
from accounts.models import Token, AuditLog
class MyTokenAuthenticationClass(authentication.BaseAuthentication):
def authenticate(self, request):
# Grab the Athorization Header from the HTTP Request
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != b'bearer':
return None
# Check that Token header is properly formatted and present, raise errors if not
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
# Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
token.last_ip = get_real_ip(request)
token.last_login = timezone.now()
token.save()
# Add the saved token instance to the request context
request.token = token
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
# At this point, insert the Log into your AuditLog table and add to request:
request.audit_log = AuditLog.objects.create(
user_id=token.user,
request_payload=request.body,
# Additional fields
...
)
# Return the Authenticated User associated with the Token
return (token.user, token)
Now, you have access to the AuditLog in your request. So you can log everything before and after validation.
I am trying to use nested routes via #list_route and #detail_route decorators. The routes work and return data but I must navigate to them manually in the address bar. They do not appear in the DefaultRouter generated ApiRoot in the browseable API. I am using Django 1.8 and Rest Framework 3.1.1.
In urls.py:
router = DefaultRouter()
router.register(r'aggregates', viewsets.AggregateViewSet, base_name='aggregate')
urlpatterns = [
url(r'^api/', include(router.urls, namespace='myapp')),
]
In viewsets.py:
class AggregateViewSet(viewsets.GenericViewSet):
queryset = models.DataAggregate.objects.order_by('id')
serializer_class = serializers.AggregateSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
#list_route(url_path='recent')
def recent_aggregates(self, request):
return Response({'message': 'herp a derp'})
When I navigate to /myapp/api, the browsable API only shows this:
HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS
{
"aggregates": "http://localhost:8000/myapp/api/aggregates/"
}
I am expecting this:
{
"aggregates": "http://localhost:8000/myapp/api/aggregates/"
"aggregates-recent": "http://localhost:8000/myapp/api/aggregates/recent"
}
I have tried various modified versions to see if it will budge, no avail. Again, these routes do function and the browseable API will show the pages for them if I navigate to them manually... but defeats the purpose of the browseable api.
I took a peek at the code for DefaultRouter (and SimpleRouter) and it does appear to discover dynamic routes...
That's true and intended it does not appear in the API by default.
I myself use that snippet to provide nested routes following HAL style (py3 code).
import urllib
from collections import OrderedDict
from rest_framework import serializers, relations
class SubNamespaceURLField(relations.HyperlinkedIdentityField):
"""Refers to a child namespace of the object, as pointed by view_name
"""
def __init__(self, namespace, *args, **kwargs):
self.namespace = namespace.strip('/')
super().__init__(*args, **kwargs)
def field_to_native(self, obj, field_name):
base = super().field_to_native(obj, field_name)
return urllib.parse.urljoin(base, self.namespace) + '/'
class HALNestedLinksField(relations.HyperlinkedIdentityField):
""" Tries to represent a list of nested links on the resource a
HAL-compliant way.
See http://stateless.co/hal_specification.html
"""
def __init__(self, endpoints, *args, **kwargs):
"""
:param endpoints list of url suffixes leading to nested operations on
the resource (ex: ['preview', 'check'])
"""
self.endpoints = endpoints
super().__init__(*args, **kwargs)
def get_attribute(self, obj):
return obj
def to_representation(self, value):
links = OrderedDict()
prefix = super().to_representation(self.get_attribute(value))
for i in self.endpoints:
# We consider if it contains a dot its a content-type indication,
# so no trailing slash
if '.' in i:
suffix = ''
else:
suffix = '/'
links[i] = {'href':
urllib.parse.urljoin(prefix, i) + suffix}
return links
Then use it in your serializer with :
class SomeModelSerializer(serializers.HyperlinkedModelSerializer):
_links = HALNestedLinksField(['revalidate'], # you detail_route names
view_name='somemodel-detail')
class Meta:
model = SomeModel
fields = ('url',
'date',
'_links') # do not forget it
And you'll get a _links attribute with all the related routes you declared in your HALNestedLinksField.