I have a model:
class Route(models.Model):
name = models.CharField("Номер маршрута", max_length=100)
evotor_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="пользователь эвотор", on_delete=models.CASCADE)
nds_CHOICES = (
(0, 'БЕЗ НДС'),
(1, 'Основная ставка 0%'),
(2, 'Основная ставка 10%'),
(3, 'Расчётная ставка 10%'),
(4, 'Основная ставка 18%'),
(5, 'Расчётная ставка 18%')
)
nds = models.IntegerField('НДС', choices=nds_CHOICES, default=0)
def __str__(self):
return self.name
class Meta:
verbose_name = "Номер маршрута"
verbose_name_plural = "Номера маршрутов"
And I have serializer for that model:
class RouteSerializer(serializers.ModelSerializer):
class Meta:
model = Route
fields = ("id", "name", "nds")
My APIView
class CarView(APIView):
authentication_classes = (BearerAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
if 'route_id' not in request.GET or request.GET['route_id']=="":
#return Response({'message': 'Не указан ID маршрута'}, status=status.HTTP_400_BAD_REQUEST)
car = Car.objects.filter(evotor_user=request.user)
else:
car = Car.objects.filter(route__id=request.GET['route_id'])
serializer = CarSerializer(car, many=True)
print(serializer.data)
return Response(serializer.data)
use this serializer
class CarSerializer(serializers.ModelSerializer):
route = RouteSerializer
class Meta:
model = Car
fields = ("id", "name", "route")
and must return me output like this:
[
{
"id": 1,
"name": "dfgdfgdfg",
"route": {
'id': 1,
'name': 'route_name',
'nds': 0,
}
}
]
But it returns:
[
{
"id": 1,
"name": "dfgdfgdfg",
"route": 1
}
]
CarSerializer.route declaration is wrong. It must be an instance of RouteSerializer, like this:
class CarSerializer(serializers.ModelSerializer):
route = RouteSerializer(read_only=True)
class Meta:
model = Car
fields = ("id", "name", "route")
This is called "nested relationship", see docs at https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
Related
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
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!
I can not update records with a patch request, I have seen that I must add the update method, but I can not understand how it actually works. I'm new to django.
The modes is mysql:
model.py
class Medida(models.Model):
descripcion = models.CharField(max_length = 100)
class Meta:
ordering = ('descripcion',)
class Sucursal(models.Model):
descripcion = models.CharField(max_length = 100)
class Meta:
ordering = ('descripcion',)
class Item(models.Model):
codigo = models.CharField(max_length = 4)
descripcion = models.CharField(max_length = 100)
class Meta:
ordering = ('codigo',)
class Almacen(models.Model):
item = models.ForeignKey(Item, on_delete = models.CASCADE)
peso = models.FloatField()
medida = models.ForeignKey(Medida, on_delete = models.CASCADE)
sucursal = models.ForeignKey(Sucursal, on_delete = models.CASCADE)
class Meta:
ordering = ('item',)
serializers.py
class MedidaSerializer(serializers.ModelSerializer):
class Meta:
model = Medida
fields = ('id', 'descripcion')
class SucursalSerializer(serializers.ModelSerializer):
class Meta:
model = Sucursal
fields = ('id', 'descripcion')
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'codigo', 'descripcion')
class AlmacenSerializer(serializers.ModelSerializer):
medida = MedidaSerializer(read_only = True)
item = ItemSerializer(read_only = True)
sucursal = SucursalSerializer(read_only = False)
class Meta:
model = Almacen
fields = ('id', 'item', 'peso', 'medida', 'sucursal')
The json answer is correct, but when you want to modify, in this case a branch with an object. Does not work.
For example:
Request:
{
"id": 2,
"item": {
"id": 2,
"codigo": "0002",
"descripcion": "Anilina"
},
"peso": 2000.1,
"medida": {
"id": 2,
"descripcion": "Kilogramos"
},
"sucursal": {
"id": 2,
"descripcion": "Magalena"
}
}
Patch:
{
"id": 2,
"item": {
"id": 2,
"codigo": "0002",
"descripcion": "Anilina"
},
"peso": 2000.1,
"medida": {
"id": 2,
"descripcion": "Kilogramos"
},
"sucursal": {
"id": 1,
"descripcion": "La Plata"
}
}
Thanks for taking the time to read my query and, thank you in advance for the response.
Try this code into your AlmacenSerializer.
def update(self, instance, validated_data):
item_data = validated_data.pop('item')
medida_data = validated_data.pop('medida')
sucursal_data = validated_data.pop('sucursal')
item = instance.item
for k, v in item_data.items():
setattr(item, k, v)
item.save()
medida = instance.medida
for k, v in medida_data.items():
setattr(medida, k, v)
medida.save()
sucursal = instance.sucursal
for k, v in sucursal_data.items():
setattr(sucursal, k, v)
sucursal.save()
instance.peso = validated_data.get('peso', instance.peso)
instance.save()
return instance