We have a nested serializer that we would like to "flatten". But I'm not having much luck finding how to achieve this in the docs.
Here is the current output.
{
"user_inventory": "UOHvaxFa11R5Z0bPYuihP0RKocn2",
"quantity": 1,
"player": {
"card_id": "c69c0808328fdc3e3f3ee8b9b7d4a7f8",
"game": "MLB The Show 22",
"name": "Jesus Tinoco",
"all_positions": [
"CP"
]
}
}
Here is what I'd like:
{
"user_inventory": "UOHvaxFa11R5Z0bPYuihP0RKocn2",
"quantity": 1,
"card_id": "c69c0808328fdc3e3f3ee8b9b7d4a7f8",
"game": "MLB The Show 22",
"name": "Jesus Tinoco",
"all_positions": [
"CP"
]
}
Here is how the serializers are setup:
class PlayerProfileSerializer(serializers.ModelSerializer):
class Meta:
model = PlayerProfile
fields = (
'card_id',
'game',
'name',
'all_positions',
)
class UserInventoryItemSerializer(serializers.ModelSerializer):
player = PlayerProfileSerializer()
class Meta:
model = UserInventoryItem
fields = (
'user_inventory',
'quantity',
'player',
)
Here is the view:
class OwnedInventoryView(viewsets.ModelViewSet):
serializer_class = UserInventoryItemSerializer
filterset_class = UserInventoryItemFilter
def get_queryset(self):
order_by = self.request.query_params.get('order_by', '')
if order_by:
order_by_name = order_by.split(' ')[1]
order_by_sign = order_by.split(' ')[0]
order_by_sign = '' if order_by_sign == 'asc' else '-'
return UserInventoryItem.objects.filter(user_inventory=self.kwargs['user_inventory_pk']).order_by(order_by_sign + order_by_name)
return UserInventoryItem.objects.filter(user_inventory=self.kwargs['user_inventory_pk'])
You can use .to_representation() method to alter data structure:
class UserInventoryItemSerializer(serializers.ModelSerializer):
player = PlayerProfileSerializer()
class Meta:
model = UserInventoryItem
fields = (
'user_inventory',
'quantity',
'player',
)
def to_representation(self, instance):
representation = super().to_representation(instance)
player = representation.pop('player')
for key, value in player.items():
representation[key] = value
return representation
Related
I have a puzzle.
These are my models:
class StatusGroup(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class StatusDetail(models.Model):
action = models.CharField(choices=[("CORRECT", "CORRECT"),
("INCORRECT", "INCORRECT")],
max_length=64)
status_group = models.ForeignKey(to=StatusGroup,
on_delete=models.CASCADE,
related_name="status_details")
def __str__(self):
return f"Detail: {self.action}"
serializers:
class StatusDetailSerializer(serializers.ModelSerializer):
class Meta:
model= models.StatusDetail
fields = "__all__"
class StatusGroupSerializer(serializers.ModelSerializer):
status_details = StatusDetailSerializer(many=True)
class Meta:
model = models.StatusGroup
fields = [
"pk",
"status_details",
"name"
]
And a view:
class Status(viewsets.ModelViewSet):
queryset = models.StatusGroup.objects.all()
serializer_class = serializers.StatusGroupSerializer
authentication_classes = []
permission_classes = [permissions.AllowAny]
filter_backends = (DjangoFilterBackend,)
filterset_fields = ['status_details__action']
When I hit localhost:8000/api/status?status_details__action=INCORRECT
I get:
[
{
"pk": 2,
"status_details": [
{
"id": 3,
"action": "CORRECT",
"status_group": 2
},
{
"id": 4,
"action": "INCORRECT",
"status_group": 2
}
],
"name": "Mixed"
}
]
Whereas I would like to have:
[
{
"pk": 2,
"status_details": [
{
"id": 4,
"action": "INCORRECT",
"status_group": 2
}
],
"name": "Mixed"
}
]
How do I force Django to filter the related objects? I can get the result I want in SQL console, but Django adds, all the related objects that belong to the StatusGroup.
I have a misconception, but I don't know what that is.
try this way (using query set):
from django.db.models import Prefetch
queryset = StatusGroup.objects.prefetch_related(
Prefetch('status_details', queryset=StatusDetail.objects.filter(action='what_you_want'), to_attr='action'))
and in your serializer class:
class StatusGroupSerializer(serializers.ModelSerializer):
status_details = StatusDetailSerializer(source='action', many=True, read_only=True)
First you can create a FilterSet class like this:
from django_filters import rest_framework as filters
class StatusGroupFilter(filters.FilterSet):
action = filters.CharFilter(
field_name="status_details__action",
lookup_expr="iexact"
)
class Meta:
model = StatusGroup
fields = ["action"]
Then in your views:
class Status(viewsets.ModelViewSet):
queryset = models.StatusGroup.objects.all()
serializer_class = serializers.StatusGroupSerializer
authentication_classes = []
permission_classes = [permissions.AllowAny]
filter_backends = (DjangoFilterBackend,)
# Set the filterset class here
filterset_class = StatusGroupFilter
Then in the URL you can call localhost:8000/api/status?action=INCORRECT
So I have two models Invoice and Items. Invoices can have many Items. Here's how they are defined:
class Invoice(models.Model):
customer_name = models.CharField(max_length = 200, verbose_name='Customer Name')
customer_phone = models.IntegerField(null = True, blank = True, verbose_name='Customer Phone')
customer_address = models.TextField(null = True, blank = True, verbose_name='Customer Address')
invoice_id = models.UUIDField(primary_key = True, unique = True, default=uuid.uuid4, verbose_name='Invoice ID')
invoice_date = models.DateField(auto_now_add=True, verbose_name='Invoice Date')
def __str__(self):
return self.customer_name + ' - ' + str(self.invoice_id)
class Meta:
verbose_name = 'Invoice'
verbose_name_plural = 'Invoices'
class Items(models.Model):
invoice = models.ForeignKey(Invoice, on_delete = models.CASCADE, related_name='invoice')
item_name = models.CharField(max_length = 200, null = False)
item_quantity = models.IntegerField()
item_price = models.IntegerField()
item_id = models.AutoField(primary_key=True)
def __str__(self):
return self.item_name
class Meta:
verbose_name = 'Items'
verbose_name_plural = 'Items'
I want to implement two API endpoints, one to get the list of invoices, and another to get a specific invoice with its items.
For example, /api/ will return all invoices
[
{
"customer_name": "John Doe",
"customer_phone": 111666,
"customer_address": "Palo Alto, California",
"invoice_id": "a8aeb5a8-5498-40fd-9b4f-bb09a7057c71",
"invoice_date": "2022-05-04"
},
{
"customer_name": "Cassian Green",
"customer_phone": 111000,
"customer_address": "2112 Illinois Avenue",
"invoice_id": "7d7b7878-3ffc-4dd2-a60a-fa207c147860",
"invoice_date": "2022-05-04"
},
{
"customer_name": "Chelsea Davis",
"customer_phone": 666777,
"customer_address": "2260 Coplin Avenue",
"invoice_id": "3dda2054-49d7-49dc-9eba-ddc0fdacfd3b",
"invoice_date": "2022-05-04"
},
{
"customer_name": "Derek Elliott",
"customer_phone": 111100,
"customer_address": "3236 Sundown Lane",
"invoice_id": "da112c0d-aff4-43d3-a465-910cc1483fc5",
"invoice_date": "2022-05-04"
}
]
and now if I request a specific invoice with its items, like /api/a8aeb5a8-5498-40fd-9b4f-bb09a7057c71 should return the following response
{
"invoice_id": "a8aeb5a8-5498-40fd-9b4f-bb09a7057c71",
"items": [
{
"item_name": "PC Cabinet",
"item_quantity": 1,
"item_price": 200
},
{
"item_name": "Motherboard",
"item_quantity": 1,
"item_price": 1000
},
{
"item_name": "PSU",
"item_quantity": 1,
"item_price": 300
}
]
}
Here are the serializers:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = [
'item_name',
'item_quantity',
'item_price',
]
class InvoiceSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Invoice
fields = [
'customer_name',
'customer_phone',
'customer_address',
'invoice_id',
'invoice_date',
'items'
]
And the views:
#api_view(['GET',])
def InvoiceList(request):
if request.method == 'GET':
queryset = Invoice.objects.all()
serializer_class = InvoiceSerializer(queryset, many=True)
return Response(serializer_class.data)
#api_view(['GET',])
def InvoiceDetail(request, pk):
if request.method == 'GET':
queryset = list(Items.objects.filter(invoice = pk))
serializer_class = ItemSerializer(instance=queryset, many=True)
return Response(serializer_class.data)
The first API endpoint is working as expected but the I need help with implementing the second one. How can I get that nested response?
Thanks.
Your related_name of invoice in Items model is wrong, you should set it as "items" because you are going to call that from Invoice model like that.
Also if you want to have different response from that second API you should change it to use other serializer than InvoiceList:
#api_view(['GET',])
def InvoiceDetail(request, pk):
if request.method == 'GET':
queryset = list(Items.objects.filter(invoice = pk))
serializer_class = InvoiceDetailSerializer(instance=queryset, many=True)
return Response(serializer_class.data)
I changed name of serializer to InvoiceDetailSerializer so you need to create new serializer:
class InvoiceDetailSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Invoice
fields = [
'invoice_id',
'items'
]
you can try something like this. (not tested)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Items
fields = ['item_name', 'item_quantity', 'item_price',]
class InvoiceDetailsSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Invoice
fields = ['id', 'items']
#api_view(['GET',])
def InvoiceDetail(request, pk):
if request.method == 'GET':
invoice = Invoice.objects.get(id=pk)
serializer_class = InvoiceDetailsSerializer(instance=invoice)
return Response(serializer_class.data)
I use the latest version of The Django REST Framework,
and The table in model is many-to-many related
My current model code looks like this:
model.py
class LvsDeploy(models.Model):
MASTER = 'MASTER'
BACKUP = 'BACKUP'
ROLE_CHOICES = (
(MASTER, 'MASTER'),
(BACKUP, 'BACKUP')
)
id = models.AutoField(primary_key=True)
cluster_name = models.CharField(max_length=30)
dip = models.CharField(max_length=15, verbose_name='DR IP')
role = models.CharField(max_length=10, choices=ROLE_CHOICES, verbose_name="role")
class LoadBalance(models.Model):
id = models.AutoField(primary_key=True)
lb_name = models.CharField(max_length=30, verbose_name='load balance')
cluster = models.ManyToManyField('LvsDeploy',related_name='cluster')
vip = models.CharField(max_length=50, verbose_name='VIP')
port = models.IntegerField(verbose_name='port')
def __str__(self):
return self.lb_name
My serializer code looks like this:
serializers.py
class LvsDeployReadOnly(serializers.ModelSerializer):
class Meta:
model = LvsDeploy
fields = '__all__'
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.RelatedField(source='cluster.name',read_only=True)
cluster = LvsDeployReadOnly(many=True)
##rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
##extra_fields = ['rs','cluster','cluster_name']
extra_fields = ['rs','cluster','cluster_name']
My views code:
class BalanceList(views.APIView):
def get(self, request):
queryset = LoadBalance.objects.all()
serializer = LoadBalanceSerializer(queryset, many=True)
print(serializer.data)
return Response(serializer.data)
and request actual output:
[
{
"id": 2,
"cluster_name": null,
"cluster": [
{
"id": 1,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"id": 2,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
But the cluster_name filed value is same in the dictionary of lists .
I want the output to look like this:
[
{
"id": 2,
"cluster_name": "lvs_sz01",
"cluster": [
{
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
How should I change it? Can you help me ?
I solved it myself with the following method:
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.SerializerMethodField()
cluster = LvsDeployReadOnly(many=True, read_only=True)
rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
extra_fields = ['rs','cluster','cluster_name']
def get_cluster_name(self,obj):
print(type(obj.lb_name))
lvs_cluster = obj.cluster.all()
cluster_name = [ i.cluster_name for i in lvs_cluster ][0]
print(cluster_name)
return cluster_name
Thanks!
Django Rest Framework - Group data by its parent tag
I have 3 serializers, One for tactics, one for techniques, one for sub techniques, and I'll be adding the sub techniques serializer as an explicit field to the techniques serializer.
models
class Tag(models.Model):
_id = models.CharField(max_length=10)
name = models.CharField(max_length=100)
def __str__(self):
return self._id
def __unicode__(self):
return self._id
class ModelA(models.Model):
_id = models.CharField(max_length=10)
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
queries = ArrayField(models.TextField(),null=True, blank=True, default=list)
def __str__(self):
return self._id
def __unicode__(self):
return self._id
class ModelB(models.Model):
subtitle = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__(self):
return self.subtitle
serializers
# serializers
class TagSerializer(serializers.ModelSerializer):
def to_representation(self, value):
print(value)
return value.name
class Meta:
model = Tag
fields = ('name',)
class QueriesSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = '__all__'
class ASerializer(serializers.ModelSerializer):
queries = QueriesSerializer(source='modelb_set', many=True)
tag = TagSerializer(many=True)
class Meta:
model = ModelA
fields = ('_id','title', 'tag','queries',)
class EndUserSerializer(serializers.ModelSerializer):
data = ??
class Meta:
model = ??
fields = '__all__'
I want to create a data visualization API where I need to use the results of ASerializer by their given tag in order to categorize each object into categories based on their associated tag if the object contains two tags, then it should be displayed in both . e.g
desired response
{
[
{
"tag": "Biologyl,
"data": [
{
"_id": str
"title": str
"tag": []
"queries": []
}
]
},
{
"tag": "Denoi2,
"data": [
{
"_id": str
"title": str
"tag": []
"queries": []
}
]
},
]
}
actual response
{
"_id": str
"title": str
"tag": []
"queries": []
}
viewset
#viewsets
class BooksViewSet(mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = ??.objects.all()
serializer_class = EndUserSerializer
You can use django-rest-multiple-models for receiving multiple querysets in the same view.
https://github.com/MattBroach/DjangoRestMultipleModels
It should look like this:
class BooksViewSet(ObjectMultipleModelAPIView):
querylist = [
{
'querylist': ModelA.objects.filter(tags__name="Biologyl"),
'serializer_class': EndUserSerializer,
'label': 'Biologyl',
},
{
'querylist': ModelA.objects.filter(tags__name="Denoi2"),
'serializer_class': EndUserSerializer,
'label': 'Denoi2',
},
....
]
You can also make it dynamic, but it seems a little bit dangerous👀.
class BooksViewSet(ObjectMultipleModelAPIView):
querylist = [
{
'querylist': ModelA.objects.filter(tags=tag),
'serializer_class': EndUserSerializer,
'label': tag.name,
}
for tag in Tag.objects.all()
]
See more https://django-rest-multiple-models.readthedocs.io/en/latest/object-options.html
(Question before: Django Rest Framework nested relationship)
I've made serializer like this:
serializers.py
from rest_framework import serializers, fields
from .models import Pegawai,Barang
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = instance.pegawai.name
return rep
class PegawaiSerializer(serializers.ModelSerializer):
barangs = BarangSerializer(read_only=True, many=True)
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
'barangs',
)
Results :
{
"pegawai": "Ryan",
"nama_barang": "burjo",
"harga_barang": "1234"
},
And How to make the result like this in the barang API when posted the data:
{
"pegawai": {"id" : 1,
"name" : "Ryan",
"alias" : "R"}
"nama_barang": "burjo",
"harga_barang": "1234"
},
Please help, cheers.
Write extra serializer and wire-up it in to_representation(..) method,
class PegawaiShortSerializer(serializers.ModelSerializer):
class Meta:
model = Pegawai
fields = (
'id',
'name',
'alias',
)
class BarangSerializer(serializers.ModelSerializer):
class Meta:
model = Barang
fields = (
'pegawai',
'nama_barang',
'harga_barang',
)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pegawai'] = PegawaiShortSerializer(instance.pegawai).data
return rep
Have you try:
rep = super().to_representation(instance)
pegawai_obj = instance.pegawai
pegawai_data = {"id":pegawai_obj.id, "name":pegawai_obj.name, "alias":pegawai_obj.alias}
rep['pegawai'] = pegawai_data
return rep
But I don't think this is the best solution.