I am struggling to figure out how to write a custom serializer in DRF to parse a complex JSON data structure that is being passed to an endpoint. The JSON looks like this:
{
"name": "new",
"site": "US",
"data": {
"settings": [],
"meta": {
"meta1":{}
}
}
}
Here is my backend code:
# views.py
class SaveData(views.APIView):
def post(self, request, *args, **kwargs):
name = request.POST.get('name')
site = request.POST.get('site')
data = request.POST.get('data')
But data always returns None. On closer inspection into the request object, the incoming JSON looks like this:
# POST attribute of request object
'name' = 'new'
'site' = 'US'
'data[settings][0] = ''
'data[meta][meta1][] = ''
Basically it looks like the nested JSON objects associated with the data key are not getting properly serialized into Python dict and list objects. I've been looking for examples of custom DRF serializers but most of the ones I've found have been for serializing Django models, but I don't need to do that. The incoming data does not directly map to my models; instead I need to do some processing before I save any of the data.
Does anyone have any advice on a custom serializer that would properly convert the data JSON into proper Python objects? I started with this but it throws an exception (When a serializer is passed a 'data' keyword argument you must call '.is_valid()' before attempting to access the serialized '.data' representation.
You should either call '.is_valid()' first, or access '.initial_data' instead.). Here is my code with the custom serializer I created:
# serializers.py
class SaveDataSerializer(serializers.Serializer):
name = serializers.CharField()
site = serializers.CharField()
data = serializers.DictField()
def create(self, validated_data):
return dict(**validated_data)
Thanks.
I was able to solve my problem by converting the JS object to JSON when passing it to $.ajax, which then DRF was able to correctly parse into Python objects. Here's a snippet of the jQuery I'm using:
$.ajax({
url: '/api/endpoint',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(ajaxData),
done: function(data) {}
...
})
Note though that jQuery will default to x-www-urlencoded as the Content-Type if you don't specify anything. Since I'm using the JSON.stringify() method to explicitly convert the ajaxData object to a JSON string the Content-Type needed to be explicitly set as well.
I got the idea from this SO answer: Convert Object to JSON string
Of course, this doesn't really answer my original question, but this is another solution that is simpler and works like I wanted. By doing this I didn't have to create any custom serializers, but I did have to modify my API view like so:
class SaveData(views.APIView):
def post(self, request, *args, **kwargs):
name = request.data.get('name')
site = request.data.get('site')
data = request.data.get('data')
After doing this the expected object types are returned
>>> type(name)
str
>>> type(site)
str
>>> type(data)
dict
Related
I have a Twitter authentication view that doesn't use a viewset so the auth can be handled on the backend. The view takes in the oauth_token & uses Twython to get the profile & create a Twitter model.
Currently I just return status 201 on success, but to alleviate the need for another request after creation, I'd like to return the created model. I have a TwitterSerializer already which defines the fields that I want to include, so I'd like to be able to reuse this if possible.
TwitterSerializer
class TwitterSerializer(serializers.ModelSerializer):
class Meta:
model = Twitter
fields = (
"id",
"twitter_user_id",
"screen_name",
"display_name",
"profile_image_url",
)
When I try to use this, I get the error that Instance of TwitterSerializer is not JSON serializable.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized })
I could return a serialized instance of the model using serializers.serialize()
serialized = serializers.serialize('json', [twitter, ])
serialized = serialized[0]
return JsonResponse({ "created": serialized })
I could pass the fields kwarg to serialize() but I don't want to have to repeat myself if I don't have to. So would it be possible to re-use my TwitterSerializer in this case? I'm having trouble finding a direct answer since most docs assume you'll be using a ViewSet when using serializerss understandably, and this feels like an edge case. I'm open to suggestions for refactoring this approach as well!
After serialization, you can get your data using data attribute of serializer like this.
serialized = TwitterSerializer(instance=twitter)
return JsonResponse({ "created": serialized.data })
You should use Django rest Response instead of JsonResponse like this
from rest_framework response
serialized = TwitterSerializer(instance=twitter)
return response.Response({ "created": serialized.data })
In my model's viewset, I have created a custom endpoint and I'm trying to serialize some quick meta information to send out.
Endpoint Declaration:
#list_route(methods=['get'], url_path='meta')
def get_meta_details(self, request):
serializer = ThingMetaSerializer
return Response(serializer.data)
ThingMetaSerializer:
class ThingMetaSerializer(serializers.Serializer):
some_data = serializers.SerializerMethodField(method_name='ret_zero')
def ret_zero(self):
return 0
Every time I run the endpoint I get the following error:
TypeError(repr(o) + " is not JSON serializable")
Any idea how I can make this work?
Edit:
I made this work using the following code in the viewset:
meta_data = {
'some_data': 0,
}
#list_route(methods=['get'], url_path='meta')
def get_meta_details(self, request):
# do some calculations
return JsonResponse(self.meta_data)
However this method does not auto generate into the Django RF Schema. If there is a better way of doing this I would love to know.
serializer = ThingMetaSerializer
This doesn't initialize the serializer. You should use:
serializer = ThingMetaSerializer(instance={'some field': 'some data'})
instead.
I'm using Django==1.10.5 and djangorestframework==3.5.3.
I have a few ModelViewSets that handle JSON API requests properly. Now, I'd like to use TemplateRenderer to add HTML rendering to those same ModelViewSets. I started with the list endpoint, and created a simple template that lists the available objects. I implemented the get_template_names to return the template I created.
Accessing that endpoint through the browser works fine when there are no objects to list, so everything related to setting up HTML renderers alongside APIs seems to work.However, when tere are objects to return the same endpoint fails with the following error:
ValueError: dictionary update sequence element #0 has length XX; 2 is required
Where XX is the number of attributes the object has.
This documentation section suggests the view function should act slightly differently when returning an HTML Response object, but I assume this is done by DRF's builtin views when necessary, so I don't think that's the issue.
This stackoverflow Q/A also looks relevant but I'm not quite sure it's the right solution to my problem.
How can I make my ModelViewSets work with both HTML and JSON renderers?
Thanks!
DRF has a brief explanation of how to do this in their documentation.
I think you'd do something like this...
On the client side, tell your endpoint what type of response you want:
fetch(yourAPIUrl, {
headers: {
'Accept': 'application/json'
// or 'Accept': 'text/html'
}
})
In your view, just check that and act accordingly:
class FlexibleAPIView(APIView):
"""
API view that can render either JSON or HTML.
"""
renderer_classes = [TemplateHTMLRenderer, JSONRenderer]
def get(self, request, *args, **kwargs):
queryset = Things.objects.all()
# If client wants HTML, give them HTML.
if request.accepted_renderer.format == 'html':
return Response({'things': queryset}, template_name='example.html')
# Otherwise, the client likely wants JSON so serialize the data.
serializer = ThingSerializer(instance=queryset)
data = serializer.data
return Response(data)
I'm trying to write an API view for my Django REST API that takes a Location object and serializers it "by hand" -- with json.dumps. Here's an example:
class LocationDetail(APIView):
def get(self, request, location_id, format=None):
l = Location.objects.get(id=location_id)
response_dict = {
"id": l.id,
"name" : l.name,
}
json_data = json.dumps(response_dict)
return Response(json_data)
this will, quite unsurprisingly, return a json object, such as this:
{"name": "Some Place", "id" : 1, ...}
This does not return a proper API response, according to https://www.hurl.it/.
But I need the API to return an Object. Here's the version where I use the built-in REST Framework's Serializer class:
serialized_location = LocationSerializer(l)
return Response(serialized_location.data)
This throws back the "proper" response, and does not cause an error in hurl.it:
Object {id: 1, name: "Some Place", …}
I'd like to figure out how to emulate the behavior of the REST serializer -- how do I get it to return an Object of json instead of just the json?
Here is the difference in images, one is clearly correct and the other just doesn't look like an API response:
REST framework serializer:
My custom json thing:
My custom json with "object" key addition:
They're weirdly different -- I want mine to be recognized as an API response too.
SOLUTION: If anyone else is interested, what you can do is just not json dump the object. Just return:
return Response(response_dict)
that simple. That will return an object appropriate for parsing.
Maybe you should just try this:
json_data = json.dumps(dict(object=response_dict))
I want to use typeahead.js in my forms in Django 1.7. Furthermore I want to implement that using class based views.
As far as I understand the problem, I need to create a view that generates a JSON response for the ajax request coming from typeahead.js.
Is it a good idea to use django-braces for that?
What I have so far is this:
from braces.views import JSONResponseMixin
[...]
class TagList(JSONResponseMixin, ListView):
"""
List Tags
"""
model = Tag
context_object_name = 'tags'
def get(self, request, *args, **kwargs):
objs = self.object_list()
context_dict = {
"name": <do something with "obs" to get just the name fields>
"color": <do something with "obs" to get just the color fields>
}
return self.render_json_response(context_dict)
That's where I'm stuck at the moment. Am I on the right path? Or would it even be possible (and easy) to go without a third party app?
Serializing non-dictionary objects¶
In order to serialize objects other than dict you must set the safe parameter to False:
response = JsonResponse([1, 2, 3], safe=False)
https://docs.djangoproject.com/en/1.10/ref/request-response/#jsonresponse-objects
Edit:
But please be aware that this introduces a potentially serious CSRF vulnerability into your code [1] and IS NOT RECOMMENDED by the Django spec, hence it being called unsafe. If what you are returning requires authentication and you don't want a third party to be able to capture it then avoid at all costs.
In order to mitigate this vulnerability, you should wrap your list in a dictionary like so:
{'context': ['some', 'list', 'elements']}
[1] https://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/
I usually use the python json library like this:
import json
from django.http import HttpResponse
class TagList(ListView):
...
context_dict = {
"name": <do something with "obs" to get just the name fields>
"color": <do something with "obs" to get just the color fields>
}
return HttpResponse(json.dumps({'context_dict': context_dict}),
content_type='application/json; charset=utf8')
But in the new Django 1.7 you have JsonResponse objects
Hope you find it useful.