I am doing some stuff with the django restframework. Very basic now, but I like to have my data coming back to me bit differently.
This is how I get the response now:
[
{
"line": "line1",
"user_text": "Some text",
"topic": "value/xrp"
},
{
"line": "line2",
"user_text": "Some text 2",
"topic": "beer/heineken/sale"
}
]
This is what I like to get:
{
"line1": {
"user_text": "Some text",
"topic": "value/xrp"
},
"line2": {
"user_text": "Some text 2",
"topic": "beer/heineken/sale"
}
}
This is my serializer:
class LineSerializer(serializers.Serializer):
line = serializers.CharField(max_length=16)
user_text = serializers.CharField(max_length=16)
topic = serializers.CharField()
This is the view
class DisplayDetailAPIView(ListAPIView):
serializer_class = LineSerializer
def get_queryset(self):
return Line.objects.filter(display__serial_number=self.kwargs['serial_number'])
And the model (as reference)
class Line(models.Model):
display = models.ForeignKey(Display, on_delete=models.CASCADE, related_name='lines')
line = models.CharField(max_length=16)
user_text = models.CharField(max_length=16, null=True, blank=True)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.line
I looked in the rest framework documentation and a bit here on stackoverflow but I could not find an answer yet.
If someone has hints for me that would be very much appreciated :)
You can override to_representation in your serializer class:
class LineSerializer(serializers.ModelSerializer):
class Meta:
model = Line
fields = ('user_text', 'topic')
def to_representation(self, instance):
data = super().to_representation(instance)
response_data = {}
response_data[instance.line] = data
return response_data
You just create a temporary data dictionary and assign the current instance line attribute to be the key of your JSON response - what you actually want to have in the end:
[
{
"line1": {
"user_text": "lorem ipsum",
"topic": ...
}
},
{
"line2": {
"user_text": "dolor sin amet",
"topic": ...
}
}
]
Note 1: You may want to use a ModelSerializer instead of Serializer in this particular case, I think it is more straightforward to use and you can access instance.line directly, without specifying it actually to be serialized.
Note 2: Here your topic field will be serialized as an id since it is a ForeignKey relation on a database level. If you want to serialize topic as a CharField from one of the actual topic's fields, have a look here.
Related
I’m working with Django Rest to have multiple “text block” objects linked with the document object. I’ve accomplished this with a simple models.ForeignKey feature so far.
However, I’m rendering all of these text blocks in multiple columns in the front end.
The Textblock model will have a column field to determine which goes to which column. Since the order of these text blocks matter, I was afraid of having them all mixed together under single "all_columns" field
So far, I figured the easiest way is to let DRF return something like the following:
{
"name": "Comparing Two Characters",
"column_A": [
{
"title": "Text Block",
"body": "lorem ipsum blah blah"
"col": 1
}
],
"column_B": [
{
"title": "Text Block 2",
"body": "lorem ipsum blah blah"
"col": 2
},
{
"title": "Text Block 3",
"body": "lorem ipsum blah blah"
"col": 2
}
]
}
How would I be able to implement something like this? I’m not sure if using related fields is even ideal for such cases. I would appreciate any help!
Here’s my current models.py code for reference:
class Document(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
# other fields
def __str__(self):
return self.name
class TextBlock(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(Document, related_name='blocks', on_delete=models.CASCADE)
col = models.IntegerField()
title = models.CharField(max_length=100)
body = models.CharField(max_length=100)
Edit:
What I'm getting returned with the updated code by sayeed910
"name": "outlineblock-test",
"desc": "",
"blocks": [
{
"url": "http://0.0.0.0:8000/api/v1/layoutblocks/7/",
"col": 3,
"title": "col3",
"placeholder": "hehehe",
"template": "http://0.0.0.0:8000/api/v1/templates/3/"
},
{
"url": "http://0.0.0.0:8000/api/v1/layoutblocks/6/",
"col": 2,
"title": "col2",
"placeholder": "hehe",
"template": "http://0.0.0.0:8000/api/v1/templates/3/"
},
{
"url": "http://0.0.0.0:8000/api/v1/layoutblocks/5/",
"col": 1,
"title": "col1",
"placeholder": "haha",
"template": "http://0.0.0.0:8000/api/v1/templates/3/"
}
],
serializers.py
class TextBlockSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TextBlock
fields = '__all__'
class DocumentSerializer(serializers.HyperlinkedModelSerializer):
blocks = LayoutBlockSerializer(many=True, read_only=True)
class Meta:
model = Document
fields = '__all__'
Try this:
from collections import defaultdict
class Document(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
# other fields
def __str__(self):
return self.name
def blocks_by_column(self):
block_group = defaultdict(list)
for block in self.blocks.all():
block_group[block.col].append(block)
return block_group
class TextBlock(models.Model):
id = models.AutoField(primary_key=True)
document = models.ForeignKey(Document, related_name='blocks', on_delete=models.CASCADE)
col = models.IntegerField()
title = models.CharField(max_length=100)
body = models.CharField(max_length=100)
If you have a ordering mechanism i.e. an order column, you can change self.blocks.all() to self.blocks.order_by(<column>).all(). You can later change the keys of blocks_group as you see fit. 1 -> column_A.
As Per OP's Edit:
You should perform the grouping operation in the serializer instead of the model
from collections import defaultdict
class DocumentSerializer(serializers.HyperlinkedModelSerializer):
blocks = LayoutBlockSerializer(many=True, read_only=True)
def to_representation(self, instance):
ret = super().to_representation(instance)
block_group = defaultdict(list)
for block in ret["blocks"]:
block_group[block["col"]].append(block)
ret["blocks"] = dict(block_group)
return ret
class Meta:
model = Document
fields = '__all__'
I'm using Django serializers to create an API which I both read from and write to.
Models:
class Biomarker(models.Model):
low_value_description = models.TextField(blank=True, null=True)
high_value_description = models.TextField(blank=True, null=True)
class BiomarkerReading(models.Model):
biomarker = models.ForeignKey(Biomarker, on_delete=models.CASCADE)
test = models.ForeignKey(Test, on_delete=models.CASCADE)
value = models.DecimalField(max_digits=30, decimal_places=8, default=0)
Serializer:
class BiomarkerReadingSerializer(serializers.ModelSerializer):
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
JSON format:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"value": 0.001
}
The above all works, and I can read and write to it with that JSON format. However I now need to add some fields from the parent model so the response looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": {
"id": 32,
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
I have got the read part working using these Serializers:
class BiomarkerDescriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Biomarker
fields = ('id', 'low_value_description', 'high_value_description')
class BiomarkerReadingSerializer(serializers.ModelSerializer):
biomarker = BiomarkerDescriptionSerializer()
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
However I can't find a way to write to it using the old json format (with "biomarker": 32 in the JSON).
I thought I would need to do something in validate or create, but I get a 400 response before it even hits those methods:
class BiomarkerReadingSerializer(serializers.ModelSerializer):
... # as above
def validate(self, data):
print('validate') # Doesn't print
data = super().validate(data)
return data
def create(self, validated_data):
print('create') # Doesn't print
return super().create(validated_data)
The example in the docs for writable-nested-serializers and the other examples I've found on SO only discuss the case of creating child records while writing to the parent record serializer, not the other way around.
I do not want to create a parent Biomarker via the API, I just need to be able to reference it by pk/id in the incoming JSON like before.
I don't mind if I have to change the names of keys to something like this for incoming:
{
"id": 617188,
"test" 71829,
"biomarker_id": 32,
"value": 0.001
}
Or something like this for the response:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"descriptions": {
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
If that makes it easier.
I hate answering my own question, but the solution was to subclass serializers.RelatedField, which is explained in advanced-serializer-usage).
This lets you separately control how the value(instance) is represented as serialised, and how the serialised data is used to retrieve or create a value (instance).
class BiomarkerDescriptionSerializer(serializers.RelatedField):
def to_representation(self, value):
data = {}
for field in ('id', 'low_value_description', 'high_value_description'):
data[field] = getattr(value, field)
return data
def to_internal_value(self, data):
return Biomarker.objects.get(id=data)
def get_queryset(self, *args):
pass
class BiomarkerReadingSerializer(serializers.ModelSerializer):
biomarker = BiomarkerDescriptionSerializer()
...
The overridden get_queryset is required, even though it does nothing.
This means data going in looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": 32,
"value": 0.001
}
Yet the data going out looks like this:
{
"id": 617188,
"test" 71829,
"biomarker": {
"id": 32,
"low_value_description": "All good",
"high_value_description": "You will die",
},
"value": 0.001
}
Thank you to those who offered answers, much appreciated
Using depth = 1 option will solve your problem.
class BiomarkerReadingSerializer(serializers.ModelSerializer):
class Meta:
model = BiomarkerReading
fields = (
'id', 'test', 'biomarker', 'value'
)
depth = 1
Take a look: https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization
I'm trying to format data when querying my API. I can retrieve my data like that :
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
But I want something like that:
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
},
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
I use a serializer like this:
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
cat1 and cat2 are dynamics, they are from another table. I don't understand how to create my arrays properly using those serializers. The category is a #Property field in my model who's a foreign key of job.
My models:
class MyModel(models.Model):
CHOICES = [(i, i) for i in range(4)]
partner = models.ForeignKey(Partner, on_delete=models.CASCADE)
job = models.ForeignKey(
Job, on_delete=models.CASCADE)
position = models.IntegerField(choices=CHOICES)
#property
def category(self):
return self.job.category.domain
def __str__(self):
return '%s | %s | %s | position: %s' % (self.partner.name, self.domain.name, self.job.name, self.position)
class Job(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
code = models.CharField(
max_length=255, unique=True)
name = models.CharField(
max_length=255)
class Category(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
code = models.CharField(
max_length=5)
name = models.CharField(max_length=255)
hourly_rate = models.FloatField(
null=True, blank=True)
How should I deal with serializers to format my data properly ?
EDIT:
I ended with something like that, except for the ListSerializer.
I used 2 ModelSerilizers
class MyModelCustomSerializer(serializers.ModelSerializer):
position = serializers.IntegerField(read_only=True)
job = serializers.CharField(source='job.name', read_only=True)
class Meta:
model = MyModel
fields = ['job', 'position']
def to_representation(self, value):
return {"position": value.position,
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
}
And
class CategoryCustomSerializer(serializers.ModelSerializer):
models = MyModelustomerSerializer(many=True)
class Meta:
model = Category
fields = ['category', 'MyModel']
def to_representation(self, value):
filters = {'job__category__domain__name': value.name}
myModels = MyModel.objects.filter(**filters)
serializer = MyModelCustomSerializer(instance=myModels, many=True,)
return {value.name: serializer.data}
But if I try to use a jobSerializer who already exist instead of
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
},
I got this error: Object of type 'Job' is not JSON serializable, but it's working anyway because i don't need all fields
I would go the direction of implementing a custom ListSerializer for the ModelSerializer and overriding its to_representation method.
from rest_framework import serializers
from collections import OrderedDict
class CustomListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
list_rep = OrderedDict()
for item in iterable:
child_rep = self.child.to_representation(item)
k, v = list(child_rep.items()).pop()
list_rep.setdefault(k, []).append(v)
return [
{k: v}
for k, v in list_rep.items()
]
Then set the model Meta to use it
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
list_serializer_class = CustomListSerializer
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
I'm struggling to write a Django GET that returns the following looking response:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876,
"item": {
"id": 9876,
"name": "item1",
"location": "California"
}
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484,
"item": {
"id": 2484,
"name": "item2",
"location": "California"
}
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Given a list_id, the response returns the basic information on the list ("id", "updated_date"), as well as the order of items in the list. Inside each item in the list order, it also grabs the related item details (nested in "item"). I'm able to get this response without the "item" details ("id", "name", "location" fields) and with no error:
{
"lists": [
{
"id": "123",
"list_order": [
{
"id": "123_1",
"order": 1,
"list_id": "123",
"item_id": 9876
},
{
"id": "123_2",
"order": 2,
"list_id": "123",
"item_id": 2484
}
],
"updated_date": "2018-03-15T00:00:00Z"
}
]
}
Again there is no error, and I can retrieve the first nested level without any issue. The problem is getting the "item" information to show within each "list_order". Below are my models, serializers, and views.
models.py
class Lists(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_lists'
class Items(models.Model):
id = models.BigIntegerField(primary_key=True)
name = models.TextField(blank=True, null=True)
location = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_items'
class ListOrder(models.Model):
id = models.CharField(null=False, primary_key=True, max_length=900)
list_id = models.ForeignKey(Lists, db_column='list_id', related_name='list_order')
item_id = models.ForeignKey(Items, db_column='item_id', related_name='items')
order = models.BigIntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'tbl_list_order'
serializers.py
class ItemsSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = '__all__'
class ListOrderSerializer(serializers.ModelSerializer):
item = ItemsSerializer(many=False, read_only=True)
class Meta:
model = ListOrder
fields = '__all__'
class ListsSerializer(serializers.ModelSerializer):
list_order = ListOrderSerializer(many=True, read_only=True)
class Meta:
model = Lists
fields = '__all__'
views.py
class ListsViewSet(generics.ListCreateAPIView):
"""
API endpoint that returns a list with its meta-information
"""
queryset = Lists.objects.all()
serializer_class = ListsSerializer
def get_queryset(self):
list_id = self.kwargs['list_id']
filters = [Q(id=list_id)]
return Lists.objects.filter(*filters)
def list(self, request, list_id):
queryset = self.get_queryset()
list_serializer = ListsSerializer(queryset, many=True)
return Response({ 'lists': list_serializer.data })
I'm pretty new to Django and like what it offers so far, though maybe I'm thinking of doing this in too much of a "SQL" way. I've read about select_related() and prefetch_related(), but not sure how I would apply it to this case. Any assistance is greatly appreciated and let me know if there's any other information I can provide.
In your ListOrderSerializer you are trying to serialize item. while in ListOrder model you used the field name item_id
Solution:
In ListOrderSerializer:
class ListOrderSerializer(serializers.ModelSerializer):
item_id = ItemsSerializer(many=False, read_only=True)
...
I have two serializers: one for the Restaurant model, another for the MainMenu model:
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
class MainMenuSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer()
main_menu_items = serializers.StringRelatedField(many=True)
class Meta:
model = MenuMain
fields = ('id', 'restaurant', 'main_menu_items')
The current output of the MainMenuSerializer is
[
{
"id": 1,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
},
"main_menu_items": [
"Fried Rice"
]
},
{
"id": 2,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B",
},
"main_menu_items": [
"Noodles"
]
}
]
But I want the RestaurantSerializer to only output once, something like this:
[
{
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
}
},
[
{
"id": 1,
"main_menu_items": [
"Fried Rice",
"Meat Balls"
]
},
{
"id": 2,
"main_menu_items": [
"Noodles"
]
}
]
]
EDIT:
models used
class Restaurant(models.Model):
name = models.CharField(max_length=100, default='')
location = models.CharField(max_length=100, default='')
class MenuMain(models.Model):
price = models.IntegerField()
restaurant = models.ForeignKey(Restaurant, related_name='main_menus')
class MenuMainItems(models.Model):
menu_main = models.ForeignKey(MenuMain, related_name='main_menu_items')
item = models.CharField(max_length=150, default='')
The more "Django REST" way of doing this is to set up a url node for each restaurant that returns all the menu items in the restaurant. For instance,
urls.py
url(r'restaurant-menu/(?P<pk>[0-9]+)$', MenuItemViewset.as_view())
In your viewset class
class MenuItemViewset(viewsets.ModelViewSet):
serializer_class = MainMenuSerializer
def retrieve(self, request, pk=None):
return Restaurant.objects.get(pk=pk).menumain_set.all()[0].menumainitems_set.all()
This of course assumes one menu per restaurant. If there are multiple menus and you want to get all of them, it's probably best practice to break it up into two calls: one for the restaurant's menu, and to get a particular menu's items. Otherwise too many assumptions are being made about the organization of the data, and that's a rather fragile design pattern.
Link to docs on fetching related objects
If you still are intent on getting a response like you originally asked for, you just need to reverse the serializer nesting, i.e. add a menu field to the RestaurantSerializer.
Here is the very simplest approach I came with. I know this can be improved.
Please comment if you have any query or improvement suggestion.
class RestaurantSerializer(serializers.ModelSerializer):
menu = serializers.SerializerMethodField()
def get_menu(self, obj):
dict_l = {}
res = MainMenu.objects.all() # filter as per your requirement
'''
flds = MainMenu._meta.local_fields
for ins in res:
for f in flds:
print f.name
'''
for ins in res:
dict_l['id'] = ins.id
dict_l['name'] = ins.name
dict_l['restaurant'] = ins.restaurant_id
# return {1: 1, 2: 2}
return dict_l
class Meta:
model = Restaurant
fields = ('id', 'name', 'menu',)