Getting response status in Django rest framework renderer class - django

I have implemented my custom Renderer like this:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
//I am hardcoding status and message for now. Which I have to update according to the response.
data = {'data': data, 'message':'ok', 'status':200 }
return super(CustomJSONRenderer, self).render(data, accepted_media_type, renderer_context)
This is working pretty good. Now I want to update status using HTTP status code of response and thus providing a custom message. So how should I achieve this?
Basically I want the response like this:
{"status":200, "data":[actual data comes here.], "message":"ok"}

Well on a different note I found out that we can get the status information. The renderer_context parameter actually contains the following information-
{'view': <ViewSet object at 0x7ff3dcc3fac0>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request object at 0x7ff3dcc37e20>, 'response': <Response status_code=400, "application/json; charset=utf-8">}
This means the renderer_context parameter is a dictionary and can be exploited in order to modify your response. For example -
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context is not None:
print(renderer_context['response'].status_code)

This is not what renderers are made for. You should use a renderer to transform the response into a certain format (json, html, csv, etc) according to the request. By default it will use the Acceptheader, but you could image to pass a querystring parameter to force a different output.
I think what you are trying to do is a custom error exception http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
Hope this helps

Related

Django rest serialization - Passing JSON to Javascript

Disclaimer - Django noob, especially REST Framework. I'm trying to create an app that for this purposes passes JSON to the template. I figured Django Rest would be ideal.
I have set up a user profile so the user can select various attributes (works fine), and now want to pass a JSON of all the user's selections to template. Think of it as a "my profile" page.
View:
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
myteam = JsonResponse(serializer.data, safe=False)
print(myteam.content) ## to see what's being passed
context = {'myteam': myteam.content}
return render(request, 'main/myteam.html', context)
Template:
<script>
console.log({{myteam}});
<\script>
Django server output:
b'[{"user": "DAVE", "GK1": {"id": 1001, "ShortName": "J. Strummer", "Club": "CLUB", "Shirt": "SHIRT.png", "ShortClub": "ETC", "ShortPos": "FW", "CurrentVal": "10", "TotalPoints": 1}, "GK2": {"id": 320, "ShortName": "D. Jones", "Club": "CLUB2", "Shirt": "ETABE.png", "ShortClub": "ETABE", "ShortPos": "GK", "CurrentVal": "4.5",
Template - Google Chrome JS console:
Uncaught SyntaxError: Invalid or unexpected token
Template - Chrome details:
console.log(b'[{"user": "mikey", "GK1": {"id": 1001, "ShortName": "J. Strummer", "Club": "ETC", "Shirt": "SHIRT.png", "ShortClub": "ETC", "ShortPos": "FW", "CurrentVal": "10.0", "TotalPoints": 1}, // lot's more of this
Noob Conclusion:
I don't seem to be getting a "clean" JSON object passed to the template from the server. Possibly due to the b'[ .... is this bytes literal?
Perhaps I should be using another method to pass the JSON to the template?
TWIST - in my testing I followed the REST tutorial and was able to setup a simple view which returned JsonResponse:
player = Player.objects.all()
serializer = PlayerSerializer(player, many=True)
return JsonResponse(serializer.data, safe=False)
Now if you go to this url mapping it displays in browser a perfect JSON example. I don't see why there should be a difference between
myteam = JsonResponse(serializer.data, safe=False)
and
return JsonResponse(serializer.data, safe=False)
I've been stuck on this on and off for literally days now so any help would be hugely appreciated.
I believe you may need to do this:
from rest_framework.renderers import JSONRenderer
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
content = JSONRenderer().render(serializer.data)
return JsonResponse(content)
Or this:
from rest_framework.renderers import JSONRenderer
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
return JsonResponse(serializer.data)
Rather than creating a JsonResponse object and passing it back through context.
There is another alternative, however, to the above as you'll want your endpoint to be extremely flexible.
Such API querying functionality is available from a 3rd-party package. I use this a lot, and find it works really well:
pip install djangorestframework-queryfields
Declare your serializer like this:
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
Then the fields can now be specified (client-side) by using query arguments:
GET /identities/?fields=id,data
Exclusion filtering is also possible, e.g. to return every field except id:
GET /identities/?fields!=id
There are a few things going on here.
Firstly, you have taken the rendered output of a response and passed the content back into a template. This isn't the right thing to do; you should skip the JsonResponse altogether and pass serializer.data into a renderer before sending it to the template.
serializer = ProfileSerializer(profile, many=True)
data = JSONRenderer().render(serializer.data)
context = {'myteam': data}
return render(request, 'main/myteam.html', context)
Secondly, the encoding is due to Django templates' automatic HTML escaping. You should mark your JSON as safe.
console.log({{ myteam|safe }});

django lazy translation sometimes appears in response.data in unit tests

I'm writing unit tests for my django api written with django-rest-framework, and I'm encountering seemingly inconsistent response data from calls that generate 400_BAD_REQUEST.
When I make a request that fails because it references an invalid primary key, repr(response.data) is a string that I can check for the substring "Invalid pk". But when I make a request that fails because it's trying to create a non-unique object, repr(response.data) contains {'name': [<django.utils.functional.__proxy__ object at 0x7f3ccdcb26d0>]} instead of the expected {'name': ['This field must be unique.']}. When I make an actual POST call to the real server, I get the expected 400 response with {'name': ['This field must be unique.']}.
Here's a code sample:
class GroupViewTests(APITestCase):
def test_post_existing_group(self):
"""
Use POST to update a group at /groups
"""
# create a group
# self.group_data returns a valid group dict
data = self.group_data(1)
url = reverse('group-list')
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# create the same group again
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# this fails
self.assertIn('must be unique', repr(response.data))
def test_create_group_with_nonexistent_user(self):
"""
Create a group with an invalid userid in it.
"""
url = reverse('group-list')
data = self.group_data(5)
data.update({'users': ['testnonexistentuserid']})
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# this succeeds
self.assertIn('Invalid pk', repr(response.data))
I'm using only the default middleware.
From what I've found online, the __proxy__ object is a representation of a lazy translation, which can be rendered as a string by calling unicode(obj) or obj.__unicode__. And indeed, response.data['name'][0].__unicode__ does return the expected value, but calling unicode(response.data) instead of repr(response.data) still returns the object identified by memory address.
Can anyone explain what's going on here, and how to fix it? I can work around the issue for now, but I'd like to know the "real way" to fix the problem. Is it possibly a bug in django or django-rest-framework?
Use response.content instead of response.data.
response.content contains the json output just like your client will receive it. It's also useful if you have custom renderer that change the response because response.data is not rendered, but response.content yes.
E.g :
import json
# we have to parse the output since it no more a dict
data = json.loads(response.content)
# if `response.content` is a byte string, decode it
data = json.loads(response.content.decode('utf-8'))

HttpResponse object becomes string when passed to assertContains

I have a strange problem in a Django template test. When the test executes my view, the view returns an HttpResponse object. However, when I then pass that response object to the Django TestCase assertContains method, the response object becomes a string. Since this string doesn't have a 'status_code' attribute like a response object does, the test fails. Here's my code:
template_tests.py
from django.test import TestCase
from django.test.client import RequestFactory
class TestUploadMainPhotoTemplate(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_user_selects_non_jpeg_photo_file(self):
"""
User is trying to upload a photo file via a form
with an ImageField. However, the file doesn't have
a '.jpg' extension so the form's is_valid function, which
I've overridden, flags this as an error and returns False.
"""
with open('photo.png') as test_photo:
request = self.factory.post(reverse('upload-photo'),
{'upload_photo': '[Upload Photo]',
'photo': test_photo})
kwargs = {'template': 'upload_photo.html'}
response = upload_photo(request, **kwargs)
# pdb.set_trace()
self.assertContains(response, 'Error: photo file must be a JPEG file')
When I run this code in the debugger and do 'type(response)' before I call assertContains, I can see that 'response' is a HttpResponse object. However, when assertContains is called, I get this error:
AttributeError: 'str' object has no attribute 'status_code'
I set an additional breakpoint in the assertContains method at the location .../django/test/testcases.py:638:
self.assertEqual(response.status_code, status_code...
At this point, when I do 'type(response)' again, I see that it has become a string object and doesn't have a status_code attribute. Can anyone explain what's going on? I've used this same test pattern successfully in a dozen other template tests and it worked in all of them. Could it have something to do with the fact that this test involves uploading a file?
Thanks.
I had a similar problem and solved it by looking at assertContains, it doesn't really help you but who knows ?
void assertContains( SimpleTestCase self, WSGIRequest response, text, count = ..., int status_code = ..., string msg_prefix = ..., bool html = ... )
Asserts that a response indicates that some content was retrieved
successfully, (i.e., the HTTP status code was as expected), and that
text occurs count times in the content of the response.
If count is None, the count doesn't matter - the assertion is true
if the text occurs at least once in the response.
Could it have something to do with the fact that this test involves uploading a file?
Sure, as I successfully wrote my test for a simple HttpResponse :
response = self.client.get('/administration/', follow=True)
self.assertContains(response, '<link href="/static/css/bootstrap.min.css" rel="stylesheet">',msg_prefix="The page should use Bootstrap")
So I am not really helping, but maybe this could help somebody a little.
I had a similar problem handling Json Response .
self.assertEquals(json.loads(response.content),{'abc': True})
Following fixed the problem for me.

django-tastypie to have JSON response

What I meant exactly was, I would like to have JSON response when I modify the obj_create(). I've implemented the UserSignUpResource(ModelResource) and inside the obj_create(), I did some validation and when it fails, I raise BadRequest(). However, this doesn't throw out JSON. It throws out String instead.
Any idea if I can make it throw out {'error': 184, 'message': 'This username already exists'} format? Or am I not suppose to modify obj_create()? Or what should I do instead?
Many help, thanks.
Cheers,
Mickey
easy that, i've just created a little helper method in tastypies http module:
import json
#tastypies HttpResponse classes here...
def create_json_response(data, http_response_class):
return http_response_class(content=json.dumps(data), content_type="application/json; charset=utf-8")
then you can simply say:
from tastypie.http import HttpNotFound, create_json_response
#choose HttpNotFound, HttpCreated whatever...
raise ImmediateHttpResponse(create_json_response({"error":"resource not found"}, HttpNotFound))
You should use the error_response method from the resource.
Something like:
def obj_create(self, bundle, **kwargs):
# Code that finds Some error
my_errors = {"error": ["Some error"]}
raise ImmediateHttpResponse(response=self.error_response(bundle.request, my_errors))
Usually you would call super and the errors should arise from the tastypie validation process. The exception would be automatically thrown (with the errors dictionary being saved on the bundle.errors property).

Get a list of objects in tastypie (in another view)

I'm trying to get a tastypie response to use in another view. I've seen the recipe in the cookbook. Problem is, I'd like to get the list view. In my case, /api/v1/source/. Here's what I've got so far:
sr = SourceResource()
objs = sr.get_object_list(request) # two objects returned
bun = sr.build_bundle(data=objs, request=request)
jsondata = sr.serialize(None, sr.full_dehydrate(bun), 'application/json')
Of course this all falls apart. bun.data doesn't have the required characteristics (a single object). So, has anyone done this successfully? How is it done?
Here's what I've come up with. I don't especially like that both the request and the QueryDict are copied, but I can't think of anything else at the moment, other than copying big portions of the tastypie code.
from copy import copy
from django.views.generic import TemplateView
from incremental.sources.resources import SourceResource
resource = SourceResource()
class AppView(TemplateView):
'Base view for the Source parts of the app'
template_name = 'sources/base.html'
def get_context_data(self, **data):
'get context data'
tmp_r = copy(self.request)
tmp_r.GET = tmp_r.GET.copy()
tmp_r.GET['format'] = 'json'
data.update({
'seed': resource.get_list(tmp_r).content
})
return data
In order to avoid the request copying stuff, you can set json as the default format, for instance in your Resource you can overload the following method:
SourceResource(Resource):
def determine_format(self, request):
return "application/json"