Django Writable Nested Serializers Update - python

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

Related

How do I "flatten" a nested serializer in DRF?

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

How can I serialize one to many models in Django Rest?

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)

django drf about many-to-many RelatedField custom field

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!

Serializer return incorrect output

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

How to get the value of the key in the serializer in manytomanyfield in Django Rest Framework?

I'm having a problem with displaying the data during serialization.
This is my model:
from django.db import models
class Paradigmn(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Language(models.Model):
name = models.CharField(max_length=50)
paradigm = models.ForeignKey(Paradigmn, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Programmer(models.Model):
name = models.CharField(max_length=50)
languages = models.ManyToManyField(Language, related_name='languages')
def __str__(self):
return self.name
And this is my serializer:
from languages.models import Language, Paradigmn, Programmer
class LanguageSerializer(serializers.ModelSerializer):
paradigms = serializers.ReadOnlyField(source='paradigm.name')
class Meta:
model = Language
fields = ('id', 'name', 'paradigms')
class ParadigmnSerializer(serializers.ModelSerializer):
class Meta:
model = Paradigmn
fields = ('id', 'name',)
class ProgrammerSerializer(serializers.ModelSerializer):
languages = LanguageSerializer(many=True, read_only=True)
class Meta:
model = Programmer
fields = ('id', 'name', 'languages')
And this is the result:
[
{
"id": 1,
"name": "Ryan",
"languages": [
{
"id": 1,
"name": "Java",
"paradigms": "Object-Oriented"
}
]
},
{
"id": 2,
"name": "Jean",
"languages": [
{
"id": 3,
"name": "Python",
"paradigms": "Object-Oriented"
}
]
},
{
"id": 3,
"name": "Michael",
"languages": [
{
"id": 2,
"name": "Elixir",
"paradigms": "Functional"
}
]
}
I just want to show on the languages array, the name of the language instead of the all the details of the language array. What is the best solution for this?
One solution:
from rest_framework.serializers import SerializerMethodField
class ProgrammerSerializer(serializers.ModelSerializer):
languagelist = SerializerMethodField()
def get_languagelist(self, obj):
return [{'name': i.name} for i in obj.languages.all()]
class Meta:
model = Programmer
fields = ('id', 'name', 'languagelist')

Categories