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']
Related
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!
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']
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
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!
I'm trying to get this kind of JSON:
"Physical": {
"Strength": 1,
"Dexterity": 1,
"Stamina": 1
},
But with my Custom serializer:
class EnumField(serializers.Field):
"""
Enum objects are serialized into " 'label' : value " notation
"""
def to_representation(self, obj):
return {"{0}".format(obj.all()[0].__str__()): obj.all()[0].current_value}
class EnumListField(serializers.DictField):
child = EnumField()
And this on my model:
#property
def physical_attributes(self):
return [self.attributes.filter(attribute=attribute) for attribute
in AttributeAbility.objects.physical()]
Outputs this:
"mental_attributes": [
{
"Intelligence": 1
},
{
"Wits": 0
},
{
"Resolve": 0
}
],
What do I need to do to my field, to look like my first JSON? I don't think DictField exists anymore, which is what a few questions on SO suggested.
Your property returns a list, and you want key:value pairs, so treat them as a dictionary:
def to_representation(self, obj):
return {str(item.get()): item.get().value for item in obj}
Obviously replace .value with whatever value you want, and if the str() representation is not what you want for the key, replace that.