django rest framework view with merged results from different object serializers - json

having the following models
class TreeLifephase(DBordered):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class TreeWidth(DBordered):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
and many more like this, that contain editable attributes of my Tree objects. For a select field on the UI I want to have all available treelifephases and treewidths with one query - to have a json result that looks something like
{
"treelifephases": [
{
"id": 1,
"name": "young"
},
{
"id": 2,
"name": "medium"
},
{
"id": 3,
"name": "old"
}
],
"treewidths": [
{
"id": 1,
"name": "10-20cm"
},
{
"id": 2,
"name": "21-30cm"
},
{
"id": 3,
"name": "31-40cm"
}
]
}
I have serializers for the Models at hand and it would be awesome to have a view that could just get a list of serializers to return a resultset like the above.

You can use SerializerMethodField and you need to create a model, in order to use model serializer.
class FullTree(serializers.ModelSerializer):
full_tree = serializers.SerializerMethodField()
class Meta:
model = FullTreeModel
fields = ('treewidth','lifephase')
get_full_tree(obj):
treewidth = obj.treewidths # get all your treewidth
lifephase = obj.lifephases# get all your lifephases
//build your tree with your serializers
return your_full_tree

I came up with a good solution for my use case which I claryfied in the question above:
1st: I wrote a Serializer that only gives me the id and the name
class IDNameSerializer(serializers.BaseSerializer):
def to_representation(self, obj):
return {
'id': obj.id,
'name': obj.name
}
2nd: I wrote a view that gives me the required json response with the help of the #api-view decorator from django-rest-framework
from rest_framework.decorators import api_view
from rest_framework.response import Response
#api_view(['GET'])
def tree_select(request):
result = {}
result['treelifephases'] = IDNameSerializer(TreeLifephase.objects.all(),many=True).data
result['treewidths'] = IDNameSerializer(TreeWidth.objects.all(),many=True).data
return Response(result)
Why I chose this as an answer:
Through use of #api_view decorator, we can still use permission classes and other sugar
It gives the json response in the required format
It is easy to alter when new fields might come there is only the view to be changed
I don't need a django model only for serialization
It is simple!

Related

How to return json in python graphene resolver without backslashes before quotation marks

I have a backend server in python (Flask + Graphene) and I need to return a JSON object like this:
{
's1': "Section 1",
's2': "Section 2",
's3': "Section 3",
's4': "Section 4"
}
The resolver looks like below:
questionnaire = graphene.types.json.JSONString(
description='JSON result test')
def resolve_questionnaire(self, info: graphql.ResolveInfo):
sections = {
's1': "Section 1",
's2': "Section 2",
's3': "Section 3",
's4': "Section 4"
}
print(json.dumps(sections))
return sections
and in console I see as a result of print(json.dumps(sections)) as I expect:
user-api_1 | {"s1": "Section 1", "s2": "Section 2", "s3": "Section 3", "s4": "Section 4"}
But in GraphiQL i see all quotation marks with backslashes:
When I change the return sections to return json.dumps(sections) I get the result like this:
The question is how to properly return a JSON object in graphene resolver?
I know that there is json.replace method to use like here, but I believe that I am simply producing/passing object in wrong way.
Your initial result of
{
"data": {
"questionnaire": "{\"s1\": \"Section 1\", \"s2\": \"Section 2\", \"s3\": \"Section 3\", \"s4\": \"Section 4\"}"
}
}
is the intended behavior. After all, questionnaire resolves to a JSONString. Since it is a string it must be double quoted, thus its inner quotations must be escaped. This is according to JSON's standards.
To use that string you, would have to run some sort of JSON parser on the data.questionnaire object. In javascript, for instance, it would be something like:
var data;
// Fetching logic to get the data object from your GraphQL server
var sections = JSON.parse(data.questionaire);
// Now you can access its objects
console.log(sections.s1) // Should print "Section 1" on the dev console
However, the method described above is not ideal if the keys of sections are not predetermined (sections.s5 may be defined in one case but undefined in another). Instead, you might rather have an array that you can iterate over. To do this, you would have to define a "model" that has explicit key-value pairs. Doing this way is format suited for GraphQL, too. For instance:
import graphene
# Our new model
class Section(graphene.ObjectType):
key = graphene.String() # dictionary key
header = graphene.String() # dictionary value
# Your previous schema with modifications
class Query(graphene.ObjectType):
# questionnaire = graphene.types.json.JSONString(description='JSON result test')
# Return a list of section objects
questionnaire = graphene.List(Section)
def resolve_questionnaire(self, info: graphql.ResolveInfo):
sections = {
's1': "Section 1",
's2': "Section 2",
's3': "Section 3",
's4': "Section 4"
}
sections_as_obj_list = [] # Used to return a list of Section types
# Create a new Section object for each item and append it to list
for key, value in sections.items(): # Use sections.iteritems() in Python2
section = Section(key, value) # Creates a section object where key=key and header=value
sections_as_obj_list.append(section)
# return sections
return sections_as_obj_list
Now, if we run the query:
query {
questionnaire {
key
header
}
}
It returns a JSON array that can be iterated through.
{
"data" {
"questionnaire": [
{
"key": "s1",
"header": "Section 1"
},
{
"key": "s2",
"header": "Section 2"
},
{
"key": "s3",
"header": "Section 3"
},
{
"key": "s4",
"header": "Section 4"
},
]
}
}
Graphene now has a GenericScalar type for this.
from graphene.types import generic
...
errors = generic.GenericScalar()
You can just subclass the JSON type and replace the serialize method:
class Any(JSON):
#staticmethod
def serialize(dt):
return dt
Then instead of
questionnaire = Field(JSON)
write
questionnaire = Field(Any)
Yes, this does break the strictly-typed spirit of GraphQL, but if that's what you want to do, there's how to do it. Note that this is an output-only hack — it won't allow you to accept arbitrary structures as arguments.
In my case I have the JSON column called (details).
from graphene.types.scalars import Scalar
class ObjectField(Scalar):
''' convert the Json String into Json '''
#staticmethod
def serialize(dt):
return dt
#staticmethod
def parse_literal(node):
return node.value
#staticmethod
def parse_value(value):
return value
class CustomDiseaseFactNode(graphene.Node):
class Meta:
name = 'diseaseFactNode'
#staticmethod #this class used to get the primary key object id
def to_global_id(type, id):
return id
Call the JSONScalar from your object class
class DiseaseFactNode(SQLAlchemyObjectType):
"""DiseaseFact node."""
class Meta:
model = DiseaseFact
interfaces = (CustomDiseaseFactNode,)
details = JSONScalar()
Another approach, to return an array of json / dictionary.
I was trying to return json from a model mixin property attribute.
Basically an any type ( note: loses the benefits of typing ), helped me to return json from a list of dictionaries:
import graphene
from graphene import relay, Scalar
from graphene_django import DjangoObjectType
class DictType(Scalar):
#staticmethod
def serialize(dt):
return dt
#staticmethod
def parse_literal(node):
return node
#staticmethod
def parse_value(value):
return value
The node itself was based on a model which included a mixin:
class InvoiceFileNode(DjangoObjectType):
signed_url = graphene.String()
variable_files = graphene.List(of_type=DictType)
class Meta:
model = InvoiceFile
interfaces = (relay.Node,)
filter_fields = []
only_fields = (
"id",
"created",
"signed_url",
"variable_files",
)
def resolve_signed_url(self, *args, **kwargs):
# #property access from FileMixin
return self.signed_url
def resolve_openable_signed_url(self, *args, **kwargs):
# #property access from FileMixin
return self.openable_signed_url
The following mixin was what I was trying to get returned, but using the of_type of JSONString serialized the dictionary to a json string:
class FileMixin(object):
#property
def signed_url(self) -> str:
return get_signed_url(self.file)
#property
def variable_files(self) -> str:
sizes = []
for s in [128, 240, 720]:
item = {"width": s}
urls = []
for n in [1,2,3]:
urls.append(get_sized_url(self.file, n))
item["urls"] = urls
sizes.append(item)
return sizes
class InvoiceFile(Models, FileMixin):
created = DateTimeField()
file = CharField()
I was having issues with returning something like:
[{"width": 123, "stuff": [{"more": "stuff"}]}}
note
this will probably not work if the dictionary returned contains any sorts of functions, or objects, etc.

Django: Serializer automatically parse json field and add to common fields

I call serializer:
serializer = MySerializer(qs, many=True) when qs - QuerySets for myModel
from rest_framework.serializers import Serializer
class MySerializer(Serializer):
param1 = CharField()
param2 = IntegerField(required=False)
custom_fields = JSONField()
class Meta:
pass
Next, I just use the custom_fields and get the values manually.
Is it possible at this stage to get the fields inside this custom_fields and return them through the serializer?
custom_fields contains:
{
'custom_value1': 3,
'custom_value2': 5
}
updated: What i want to get after serializer:
{
'param1': 'value1',
'param2': 'value2',
'custom_value1': 3,
'custom_value2': 5
}
I'm afraid I exactly understand your question... You're asking that you can GET your custom field's data?
Then answer is Yes
If you just call GET request to APIView using MySerializer, you can get all data from your model.
If you want to get data from your Model, you simply using ModelSerializer. (http://www.django-rest-framework.org/api-guide/serializers/#modelserializer). ModelSerializer is much easier but you can use your own serializer, too.
Nowadays I experienced similar situation, and I can get all my extra data (same as you, it's json object)
I attach my serializer and respone. I hope it helps you.
My serialzier (extra field is JsonField)
class JobUserSerializer(serializers.ModelSerializer):
class Meta:
model = JobUser
fields = (
"email",
"is_active",
"is_staff",
"id",
"extra",
)
read_only_fields = (
"id",
)
My response (GET request to viewsets. You can see extra json data)
{
"email": "test5#test.com",
"is_active": true,
"is_staff": false,
"id": 13,
"extra": {
"last_name": "kim",
"first_name": "seul",
"gcf_spouses_name": "test",
"gcf_spouses_position": "test"
}
},
Update
I think you can use get_* method and use it.
this is example code for mine
class JobUserSerializer(serializers.ModelSerializer):
first_name_from_extra = serializers.SerializerMethodField()
class Meta:
model = JobUser
fields = (
"email",
"is_active",
"is_staff",
"id",
# "extra",
"first_name_from_extra"
)
read_only_fields = (
"id",
)
def get_first_name_from_extra(self, obj):
try:
return obj.extra['first_name']
except TypeError:
return ""
Then you can access directly "first_name" (my extra json field's key)
My response
{
"email": "test5#test.com",
"is_active": true,
"is_staff": false,
"id": 13,
"first_name_from_extra": "seul"
},
Be careful using this method: your json field SHOULD have that key. If one of your serializing model don't have the key, it raises NoneType TypeError. Or you can use try/except in get_* method.
Hope helping!

Django RestFramework returning a precomputed json

Currently I have this:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (
'id', 'f0', 'f1', 'f2')
And it returns something like this:
{
"count": 6242,
"previous": null,
"total_pages": 209,
"results": [
{
"id": 63915,
"f0": "Some stuff"
.....
},
{
"id": 63916,
"f0": "Some other stuff"
.....
}....
]
}
And this is good, but I noticed that serializing the data is actually quite expensive to do on the fly, so I would like to precompute it. So far I've managed to precompute it and store it in a jsonfield for my model, the problem is my API is now returning {'json_repersentation':{myold_response}}
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('json_representation',)
My question is, is it possible to change it so that it simply returns the json contained within json_representation field without the "overhead" of {'json_representation':{id:0, f0:label...}} and instead just simply {id:0, f0:label...}
You can override the serializer to_representation method:
def to_representation(self, instance):
data = super(MySerializer, self).to_representation(instance)
return data['json_representation']

Change structure of serialized object in Django Rest Framework (pull ID field outside of model content)

Say, you have a model with name and e-mail and used Model Serializer to serialize your data with Django Rest Framework. The output would be something like this:
[{
"id": "1",
"name": "Alex",
"email": "alex#host.com"
}, {
"id": "2",
"name": "Henry",
"email": "henry#host.com"
}]
Is there a way to "pull" a unique field outside the rest of the model content to get this?:
{
"1": {
"name": "Alex",
"email": "alex#host.com"
},
"2": {
"name": "Henry",
"email": "henry#host.com"
}
}
I am able to "externalize" the ID by overriding serializer's to_representation() method (code sample below) but I can't get rid of the rudimental wrappers - currently JRF will return everything as list[] of dict(instance) similar to this:
data = list
for item in instance:
list.append(item.as_dict())
I don't need this to be a list of dicts, I want them to do be dict of dict?
data = {}
for item in instance:
data[item.id] = item.as_dict()
here's my to_representation() code, I can only manipulate what's returned for individual instance, so it has to be a dict, but I need this dict to be merged, rather than stacked at the output when serializer.data is fully cooked.
def to_representation(self, instance):
rep = OrderedDict()
fields = self._readable_fields
id_field = filter(lambda i: i.label == "ID", fields)[0]
if id_field:
id_atr = id_field.get_attribute(instance).__str__()
else:
raise Exception('cannot serialize models without ID field')
rep[id_atr] = {}
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
if attribute is None:
rep[id_atr][field.field_name] = None
else:
rep[id_atr][field.field_name] = field.to_representation(attribute)
return rep
I was able to make it work by reformatting the original serializer output, but it's surely not a clean solution with a lot of performance impact:
def list(self, request, *args, **kwargs):
_data = super(ChatMessageViewSet, self).list(request, *args, **kwargs)
_resp = {}
for item in _data.data:
_id = item.get('id', None)
if not _id:
raise Exception('cannot serialize data without id field')
_resp[_id] = {}
for element in item:
_resp[_id][element] = item[element]
_data.data = _resp
return _data
as per #zaphod100-10 answer, I have created a custom ListSerializer class:
class ChatMessageListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
_data = {}
for item in iterable:
_data[item.id] = self.child.to_representation(item)
return _data
and it formats the data the way I need this:
but my data does not survive 'ListModelMixin':
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
it returns a list[] of just the entity ids: [1L, 2L, 3L...]
Create a custom ListSerializer for your model and override the to_representation method.
def to_representation(self, data):
# convert your data which is a queryset or a
# list of objects to dict of dicts
.....
return dict_of_dicts
After creating the ListSerializer in your main serializer class add list_serializer_class meta property.
read more about list serializer here:
http://www.django-rest-framework.org/api-guide/serializers/#listserializer

Django-rest-framework: disable nested fields

I'm working with Django REST Framework and I
I have a serializer called QuestionSerializer where I specified 3 fields that I need to have in my response: id, json and explanation.
Since I'm using JSONField in a PostgreSQL Database, my json field is a json stored inside the db and I render it using JSONSerializerField.
Here's my code:
class JSONSerializerField(serializers.Field):
""" Serializer for JSONField -- required to make field writable"""
def to_internal_value(self, data):
return data
def to_representation(self, value):
return value
class QuestionSerializer(serializers.ModelSerializer):
content = JSONSerializerField(source='json')
class Meta:
model = Question
fields = ('id', 'content', 'explanation')
So, every time I'll use QuestionSerializer my response will be something like this:
{
"id": 1,
"content": {
"question": "question",
"answers": [
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"}
],
}
"explanation": "explanation"
}
But I need to remove "content" field to have a response like:
{
"id": 1,
"question": "question",
"answers": [
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"},
{"answer": "answer"}
],
"explanation": "explanation"
}
What I should do?
Thanks!
you can make content write_only field and use it to just store data. Add two new read_only fields and use them to get data like:
class QuestionSerializer(serializers.ModelSerializer):
content = JSONSerializerField(source='json', write_only=True)
question = serializers.SerializerMethodField()
answers = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ('id', 'content','question', 'answers', 'explanation')
def get_questions(self, obj):
return obj.content['question']
def get_answers(self, obj):
return obj.content['answers']
I found the right solution. I removed 'content' field and JSONSerializerField class and I started working on my json using SerializerMethodField.
class QuestionSerializer(serializers.ModelSerializer):
question = serializers.SerializerMethodField()
answers = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ('id', 'question', 'answers', 'explanation')
def get_question(self, obj):
return obj.json['question']
def get_answers(self, obj):
return obj.json['answers']
You should update your Field so that it returns / read the content directly:
class JSONSerializerField(serializers.Field):
""" Serializer for JSONField -- required to make field writable"""
def to_internal_value(self, data):
return {'content': data}
def to_representation(self, value):
return value['content']