Displaying ForeignKey attributes in DRF - python

When I run the api, instead of getting category name and subcategory name, I get their id,
[
{
"id": 1,
"name": "StrawBerry",
"category": 1,
"subcategory": 1
}
]
I actually want something like this:
[
{
"id": 1,
"name": "StrawBerry",
"category": "Fruits",
"subcategory": "Berries"
}
]
Note: I already have category and subcategory. I just want to know how to access them.
models.py
from django.db import models
class Category(models.Model):
category = models.CharField(max_length=200)
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
class Meta:
unique_together = ('parent' , 'category')
def __str__(self):
return self.category
class SubCategory(models.Model):
subcategory = models.CharField(max_length=200)
category = models.ForeignKey('Category', null=True, blank=True)
parent = models.ForeignKey('self', blank=True, null=True, related_name='subchilren')
class Meta:
unique_together = ('parent' , 'subcategory')
def __str__(self):
return self.subcategory
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey('Category', null=True, blank=True)
subcategory = models.ForeignKey('SubCategory', null=True, blank=True)
def __str__(self):
return self.name
serializers.py
class GetProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('id', 'name', 'category', 'subcategory')
views.py
class GetProductViewSet(viewsets.ModelViewSet):
serializer_class = GetProductSerializer
queryset = Product.objects.all()

One solution would be to define the category and subcategory fields of your GetProductSerializer as StringRelatedFields:
StringRelatedField may be used to represent the target of the relationship using its unicode method.
http://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield
Or, similarly, as SlugRelatedFields:
SlugRelatedField may be used to represent the target of the relationship using a field on the target.
http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield

Related

Calculate sum of model objects in django serializers

In my app, each user has his wallets in which he can save his daily expenses.
How can I get sum of money for each instance of wallet with many expenses saved to it?
I've tried serializers.SerializerMethodField()
from django.db.models import Sum
from django.db.models import Q
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
return WalletInstance.objects.filter(Q(id=obj.id)|Q(entry__entry_type='income')).aggregate(Sum('entry__amount'))['entry__amount__sum']
And this gives me data in the correct format but the sum is wrong, for example
[
{
"id": "d458196e-49f1-42db-8bc2-ee1dba438953",
"owner": 1,
"entry": [
{
"id": 3,
"owner": 1,
"title": "dsfdsf",
"amount": 7,
"description": "sdfdsf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
},
{
"id": 4,
"owner": 1,
"title": "fesfvsdfgvbtdg",
"amount": 12,
"description": "efesf",
"entry_type": "income",
"date_added": "2022-08-13",
"entry_category": {
"id": 2,
"name": "Transport"
}
}
],
"viewable": [
"admin#gmail.com"
],
"expense_sum": 83,
"name": "dsdsds",
"date_added": "2022-08-13"
}
]
Expense_sum is 83 but amount fields are 7 and 12 so that's equal to 19
How can i get the correct number?
Models
class BudgetEntry(models.Model):
STATE= [
('income','income'),
('expenses','expenses'),
]
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner_of_entry', on_delete=models.CASCADE)
title = models.CharField(max_length=20)
amount = models.IntegerField()
description = models.CharField(max_length=60, null=True)
entry_type = models.CharField(max_length=15, choices=STATE, null=True)
entry_category = models.ForeignKey(Category, null=True, blank=True, related_name='category_of_entry', on_delete=models.SET_NULL)
date_added = models.DateField(auto_now_add=True)
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
I think ORM query for the BudgetEntry is wrong. It should be like the following:
def get_expense_sum(self, obj):
return obj.entry.filter(entry_type='income').aggregate(Sum('entry__amount'))['entry__amount__sum']
I've figured it out. Firstly I've created #property in my database to get sum of expenses, but this wasn't very useful If I ever wanted to count income so I passed the value arg to the function where the value can be expense/income
class WalletInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=30, null=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owner', on_delete=models.CASCADE)
viewable = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='can_view', blank=True)
entry = models.ManyToManyField(BudgetEntry, related_name='BudgetEntry', blank=True)
date_added = models.DateField(auto_now_add=True)
def total_amount(self, value):
queryset = self.entry.filter(entry_type=value).aggregate(
total_amount=models.Sum('amount'))
return queryset["total_amount"]
And now I'm calling this function in my serializer instead of counting there
class WalletInstanceSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
entry = BudgetEntrySerializer(many=True, read_only=True)
viewable = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='email')
expense_sum = serializers.SerializerMethodField()
class Meta:
model = WalletInstance
fields = '__all__'
def get_expense_sum(self, obj):
wallet_obj = WalletInstance.objects.get(id=obj.id)
return wallet_obj.total_amount('expenses')

How to show foreign key data values instead of urls in Django api?

I have the following codes in models.py
class Tag(models.Model):
name = models.CharField(max_length=40)
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
likes = models.IntegerField()
popularity = models.FloatField()
reads = models.IntegerField()
tags = models.ManyToManyField(Tag)
class User(models.Model):
name = models.CharField(max_length=40)
def __str__(self):
return self.name
but in localhost:8000/posts, I found out something weird: those foreign keys are shown as the urls, for example
{
"url": "http://127.0.0.1:8000/posts/1/",
"likes": 3,
"popularity": 0.25,
"reads": 59,
"author": "http://127.0.0.1:8000/users/1/",
"tags": [
"http://127.0.0.1:8000/tags/1/",
"http://127.0.0.1:8000/tags/2/"
]
}
So in this case, how can I change the displayed api into something like "author":"Any Name" and "tags": ["tag1","tag2"] instead?
You can use ModelSerializer with SerializerMethodField
class YourSerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField("get_author")
tags = serializers.SerializerMethodField("get_tags")
class Meta:
model = YourModel
fields = (YourModelFields, "author", "tags")
def get_author(self,obj):
return obj.author.name
def get_tags(self, obj):
return obj.tags.all().values("name")

How to get field name instead of foreign id in django

In my django app, i have four model tables linked using foreign keys, the problem is, when i make a query for any model table, the fields that are linked through foreign keys are returned as id's instead of the name.
My way does not work.
My customers models.py file
1st model
class Customer(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
image = models.ImageField(default='default.png', upload_to='customer_photos/%Y/%m/%d/')
data_added = models.DateField(default=datetime.now, blank=True)
def __str__(self):
return self.name
2nd model
class ShippingAddress(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="customer_ship_address")
description = models.TextField(blank=True)
frequent_customer = models.BooleanField(default=False)
address = models.CharField(max_length=50, blank=True)
zip_code = models.CharField(max_length=12, blank=True)
location = models.CharField(max_length=255)
def __str__(self):
return self.customer.name
3rd model - PaymentInvoice
class paymentInvoice(models.Model):
shipping_address_owner = models.ForeignKey(
ShippingAddress, on_delete=models.CASCADE, related_name="customer_invoice")
product = models.ManyToManyField(
Product, related_name='product_invoice')
mode = models.CharField(max_length=20, choices=paymentMode.choices, default=paymentMode.MPESA)
date = models.DateField(default=datetime.now)
invoice_id = models.CharField(
max_length=50, unique=True, default=increment_invoice_number)
quantity = models.PositiveSmallIntegerField()
status = models.CharField(
max_length=20, choices=paymentStatus.choices, default=paymentStatus.PENDING)
payment_made = models.DecimalField(max_digits=20, decimal_places=2)
def __str__(self):
return self.shipping_address_owner.customer.name
My Products models.py file
class Product(models.Model):
slug = models.CharField(max_length=200, unique=True)
name = models.CharField(max_length=200)
available = models.BooleanField(default=True)
description = models.TextField(blank=True)
image = models.ImageField(default='default_product.jpg', upload_to='product_photos')
category = models.CharField(max_length=200)
qty_amount = models.CharField(
max_length=20, choices=Qty_Choices, default='250ml')
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
My PaymentInvoice views.py file
class paymentInvoiceListCreateView(ListCreateAPIView):
"""
ListCreateAPIView executes both 'GET' and 'POST' requests. i.e listing a queryset or creating a model instance.
"""
serializer_class = paymentInvoiceSerializer
queryset = paymentInvoice.objects.all().order_by(
'-date').values(shipping_address_owner__customer)
When i make the above query the api returns the following, where the product field and shipping_address_owner field are just id's. I need there respective names instead.
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"id": 9,
"mode": "Mpesa",
"date": "2020-07-27",
"invoice_id": "INV-0006",
"quantity": 1,
"status": "Pending",
"payment_made": "500.00",
"shipping_address_owner": 9,
"product": [
1
]
},
EDIT: paymentInvoiceSerializer
class paymentInvoiceSerializer(serializers.ModelSerializer):
class Meta:
model = paymentInvoice
fields = '__all__'
You need to make some changes to your serializer.
If you just need the name and nothing more:
class paymentInvoiceSerializer(serializers.ModelSerializer):
product = serializers.SerializerMethodField()
shipping_address_owner = serializers.SerializerMethodField()
class Meta:
model = paymentInvoice
fields = '__all__'
def get_product(self, instance):
names = []
for product in instance.product.all():
names.append(product.name)
return names
def get_shipping_address_owner(self, instance):
return instance.shipping_address_owner.customer.name
You can also create different serializer for each model and pass them to their fields to get the full serialized data for them. It would be something like this:
ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
And then in your payment serializer:
class paymentInvoiceSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = paymentInvoice
fields = '__all__'
You can do the same for shipping_address_owner.
You can try like this in your serializer.
class paymentInvoiceSerializer(serializers.ModelSerializer):
shipping_address_owner = serializers.CharField(source='shipping_address_owner.customer')
class Meta:
model = paymentInvoice
fields = ['shipping_address_owner',...]

how to assign the foreign keys for this DRF nested serializer

I'm working on an app that receives clothes "vendor, product type and sizes" from an external source that i want to save, then update the Size Charts later.
but i'm not getting it to successfully save and update
here's my Models.py:
class Vendor(models.Model):
name = models.CharField(max_length=50, null=False, blank=False)
store_slug = models.CharField(max_length=100, null=False, blank=False)
is_from_user = models.NullBooleanField(null=False, default=True)
class Meta:
ordering = ('name',)
verbose_name_plural = 'Vendors'
def __str__(self):
return self.name
class ProductType(models.Model):
vendor = models.ForeignKey(Vendor,related_name='producttypes', on_delete =models.CASCADE, null=False)
name = models.CharField(max_length=50, null=False, blank=False)
country = models.CharField(max_length=50, default="US", null=False, blank=True)
gender = models.CharField(max_length=50, default="NONE", null=False)
class Meta:
ordering = ('name',)
verbose_name_plural = 'ProductTypes'
def __str__(self):
return self.name + ' | ' + self.vendor.name
class Size(models.Model):
producttype = models.ForeignKey(ProductType,related_name='sizes', on_delete =models.CASCADE, null=False)
name = models.TextField(max_length=50, null=False, blank=False)
#ready = models.NullBooleanField(default=False)
class Meta:
ordering = ('name',)
verbose_name_plural = 'Sizes'
def __str__(self):
return self.name + ' | ' + self.producttype.name
class Chart(models.Model):
size = models.ForeignKey(Size,related_name='charts', on_delete =models.CASCADE, null=False)
bust = models.DecimalField(max_digits=5, decimal_places=2, default=0.0, null=True, blank=True)
hip = models.DecimalField(max_digits=5, decimal_places=2, default=0.0, null=True, blank=True)
waist = models.DecimalField(max_digits=5, decimal_places=2, default=0.0, null=True, blank=True)
unit = models.CharField(max_length=50, default="CM", null=False, blank=True)
class Meta:
ordering = ('size',)
verbose_name_plural = 'Charts'
def __str__(self):
return self.unit + ' Chart of | '+ self.size.name + ' | '
and in my serializers.py :
class ChartSerializer(serializers.ModelSerializer):
class Meta:
model = Chart
fields = ('id', 'hip', 'bust', 'waist' , 'unit' ,'size')
class SizeSerializer(serializers.ModelSerializer):
charts = ChartSerializer( many=True)
class Meta:
model = Size
fields = ('id', 'name' ,'producttype', 'charts' )
class ProductTypeSerializer(serializers.ModelSerializer):
sizes = SizeSerializer( many=True)
class Meta:
model = ProductType
fields = ('id', 'name', 'country', 'gender','vendor', 'sizes')
class VendorSerializer(serializers.ModelSerializer):
producttypes = ProductTypeSerializer(many=True)
class Meta:
model = Vendor
fields = ('id', 'name', 'store_slug', 'producttypes')
def run_validators(self, value):
for validator in self.validators:
if isinstance(validator, validators.UniqueTogetherValidator):
self.validators.remove(validator)
super(VendorSerializer, self).run_validators(value)
def create(self, validated_data):
producttypes = validated_data.pop('producttypes')
v = Vendor.objects.get_or_create(**validated_data)
for producttype in producttypes:
sizes = producttype.pop('sizes')
p = ProductType.objects.get_or_create(vendor=v, **producttype)
for size in sizes:
charts = size.pop('charts')
s = Size.objects.get_or_create(producttype=p, **size)
for chart in charts:
c = Chart.objects.get_or_create(size=size, **chart)
return v
and my views.py:
class VendorViewSet(viewsets.ModelViewSet):
queryset = Vendor.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
return VendorReadSerializer
return VendorSerializer
class ProductTypeViewSet(viewsets.ModelViewSet):
queryset = ProductType.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
# Since the ReadSerializer does nested lookups
# in multiple tables, only use it when necessary
return ProductTypeReadSerializer
return ProductTypeSerializer
class SizeViewSet(viewsets.ModelViewSet):
queryset = Size.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
return SizeReadSerializer
return SizeSerializer
class ChartViewSet(viewsets.ModelViewSet):
queryset = Chart.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
return ChartReadSerializer
return ChartSerializer
and here is what i think the the post request should be
data = {
"name": "Adidas",
"store_slug": "slugexamplecom",
"producttypes": [
{
"name": "short",
"gender": "female",
"countr":"US",
"sizes": [
{
"name": "Small",
"charts":[{"unit":"CM",}]
},
{
"name":"Medium",
"charts":[{"unit":"CM","hip":"10","bust":"10","waist":"10"}]
},
{
"name":"Large",
"charts":[{"unit":"IN","hip":"20","bust":"20","waist":"20"}]
}
]
},
]}
My Case:
here is what i receive from an external source,
that i want to iterate over their sizes and create a not-ready chart
for later update on my dashboard.
all the fields should be updatable.
{"name": "Adidas",
"store_slug": "aSlug",
"producttypes": [{
"name": "short",
"gender": "female",
"sizes": [{"name":"small"},{"name":"Medium"},{"name":"Large"} ]}
]}
what am i doing wrong ?
How do i save and update ? and assign the foreign Key
#Currently the serializer is not valid
#if i remove the foreign keys fields i get a valid serializer with this error
#TypeError: int() argument must be a string, a bytes-like object or a number, not 'Vendor'

device_list is not shown

I am creating an api where the list of groups are shown along with the devices id that falls under that groups. For example if there is a device named Speedometer, Humidifier and they fall under Home group then my api should include
{
"id": 1,
"name": "Home"
"device_list": [
{
"id": "b45c56ioxa1"
},
{
"id": "h4oc2d5ofa9"
}
]
},
but my code does not produce device_list in the api. It only shows name and id
device_list is the list of all the devices id that are in a certain group.
Here is my code
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
device_list = DeviceIdSerializer(read_only=False, many=True, required=False, source="groups")
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'device_list')
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, format=None):
"""
Returns a list of groups
"""
reply = {}
try:
groups = DeviceGroup.objects.all()
print ('reply', groups)
reply['data'] = DeviceGroupSerializer(groups, many=True).data
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
name = models.CharField(max_length=250, blank=False, null=False)
I tried out the very same code you have except I used models.Model as the base model.
The first time I got an error
The fields option must be a list or tuple or "all". Got str.
which clearly states where your problem is.
So I changed class the fields option in DeviceIdSerializer
DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id',)
Note that I've added a comma (",") which makes fields a tuple instead of a string as it was before.
Now the data I get is
{
"id":1,
"name":"test",
"device_list":[
{
"id":"38ec7e152f9d49a38008c859a1022525"
},
{
"id":"b0d799509260474cb092899ef84ce49c"
},
{
"id":"e5c7cf8f9f5043c68c34c7b962569b08"
}
]
}
which is the same as what you are looking for...
I think your serializers need to look like this:
class DeviceIdSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = Device
fields = ('id')
class DeviceGroupSerializer(serializers.ModelSerializer):
name = serializers.StringRelatedField()
groups = DeviceIdSerializer(read_only=False, many=True, required=False)
class Meta:
model = DeviceGroup
fields = ('id', 'name', 'groups')
Or change this:
class BaseDevice(PolymorphicModel):
# User's own identifier of the product
name = models.CharField(max_length=250, blank=False, null=False)
# Any device should have a owner, right from the creation
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="device_list", null=True, blank=True)

Categories

Resources