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')
Related
models:
class Category(models.Model):
name = models.CharField(max_length=80)
created_at = models.DateTimeField(auto_now_add = True, null = True)
created_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name="created_by")
updated_at = models.DateTimeField(auto_now = True, null = True)
updated_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name="updated_by")
deleted_at = models.DateTimeField(null = True)
deleted_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name='deleted_by')
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Product(models.Model):
category = models.ManyToManyField(Category, blank=False)
name = models.CharField(max_length=100)
description = models.TextField(max_length=250)
price = models.DecimalField(max_digits=6, decimal_places=2)
quantity = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add = True, null = True)
created_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name="products_created_by")
updated_at = models.DateTimeField(auto_now = True, null = True)
updated_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name="products_updated_by")
deleted_at = models.DateTimeField(null = True)
deleted_by = models.ForeignKey(User, default = None, blank=True, null=True, on_delete=models.DO_NOTHING, related_name='products_deleted_by')
class Meta:
ordering = ("name", )
def __str__(self):
return self.name
serializer:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ("id", "name", "created_at", "created_by", "updated_at", "updated_by", "deleted_at", "deleted_by",)
class ProductSerializer(serializers.ModelSerializer):
# category = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source="category")
category = CategorySerializer(many=True)
created_by = serializers.StringRelatedField()
updated_by = serializers.StringRelatedField()
deleted_by = serializers.StringRelatedField()
class Meta:
model = Product
fields = (
"id", "name", "category", "description", "price",
"quantity", "created_at", "created_by", "updated_at",
"updated_by", "deleted_at", "deleted_by",
)
response in postman:
"id": 6,
"name": "Watch",
"category": [
{
"id": 3,
"name": "Electronics",
"created_at": "2020-12-07T12:45:32.521172Z",
"created_by": 2,
"updated_at": "2020-12-07T12:45:32.521217Z",
"updated_by": null,
"deleted_at": null,
"deleted_by": null
},
{
"id": 4,
"name": "Accessories",
"created_at": "2020-12-07T12:46:00.992857Z",
"created_by": 2,
"updated_at": "2020-12-07T12:46:00.992910Z",
"updated_by": null,
"deleted_at": null,
"deleted_by": null
}
],
"description": "Accessory for Smart Phones",
"price": "99.99",
"quantity": 0,
"created_at": "2020-12-07T14:00:12.312151Z",
"created_by": "user1",
"updated_at": "2020-12-07T14:00:12.312222Z",
"updated_by": null,
"deleted_at": null,
"deleted_by": null
}
I want to achieve something like this:
{
"category_ids": [
1,
2
],
"category": [
{
"name": "Category1"
},
{
"name": "Category2"
}
],
"creator": {
"name": "Admin"
},
"updater": {
"name": "Admin2"
}
}
my head hurts. can't find any solution to this problem. probably I am missing something or I am unaware of something.
it's many to many field. I tried to use PrimaryKeyRelatedField and source, but nothing. my biggest problem is, I don't know how to list category_ids seperately
You Can Use SerializerMethodField to achieve this
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ("name",)
class UserSerializer(serializers.Serializer):
name = serializers.CharField()
class ProductSerializer(serializers.ModelSerializer):
category_ids = serializers.SerializerMethodField()
category = CategorySerializer(many=True)
creator = UserSerializer()
updator = UserSerializer()
class Meta:
model = Product
fields = ("category_ids","category", "creator", "updator")
def get_category_ids(self, instance):
return list(instance.category.all().values_list('id', flat=True))
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',...]
I'm new in Django I want to get all informations of my contacts for a store given in parameter and all extra from another model.
Here is my code :
Models.py
class Store(models.Model):
name = models.CharField(max_length=100)
class Contact(models.Model):
id = models.CharField(primary_key=True, max_length=200)
first_name = models.CharField(max_length=100, null=True, blank=True)
last_name = models.CharField(max_length=100, null=True, blank=True)
email = models.CharField(max_length=100, null=True, blank=True)
stores = models.ManyToManyField(Store, related_name='contacts')
class DataType(models.Model):
datatype = models.CharField(max_length=10)
class DataField(models.Model):
name = models.CharField(max_length=100)
label = models.CharField(max_length=100)
datatype = models.ForeignKey(
'DataType',
on_delete=models.SET_NULL,
null=True
)
class DataFieldValue(models.Model):
contact = models.ForeignKey(
Contact,
on_delete=models.SET_NULL,
related_name='datafieldvalues',
null=True
)
datafield = models.ForeignKey(
'DataField',
on_delete=models.SET_NULL,
null=True
)
value = models.CharField(
max_length=250,
null=True,
blank=True
)
Views.py
# Contact API views.
class ContactListAPIView(generics.ListAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_class = ContactFilter
Filters.py
class ContactFilter(django_filters.rest_framework.FilterSet):
store = django_filters.CharFilter()
class Meta:
model = Contact
fields = ['store']
Serializers.py
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = '__all__'
(Additionnal) Here is the schema :
schema
this is what i want:
GET /contacts/?store=54
{
"count": 25,
"next": "<url>/contacts/?page=3",
"previous": "<url>/contacts/?page=2",
"data": [
{
"id": "a519d2cd-13c6-48f3-b391-9a7e0cee58cc",
"src_id": 356,
"first_name": "Benjamin",
"last_name": "Pavard",
"email": "benjamin.pavard#email.fr",
"datafields" : {
"age" : "eighteen"
},
"store": 54
},
....
]
}
but this is what i got :
{
"next": null,
"previous": null,
"count": 10,
"data": [
{
"id": "1234567",
"first_name": null,
"last_name": null,
"email": "cartouche#email.fr",
},
.....
}
Questions :
how can i get the "datafields" ?
"age" is a "datafield" object and "eighteen" is a datafieldvalue object.
How can i get only the store that i've previously gave in parameter?
Thanks a lot for your help whatever it was.
You need to add more fields in the ContactSerializer
class ContactSerializer(serializers.ModelSerializer):
store = serializers.SerializerMethodField()
datafields = serializers.SerializerMethodField()
class Meta:
model = Contact
fields = '__all__'
def get_datafields(self, obj):
result = {}
for data_field_value in obj.datafieldvalues.all().select_related('datafield'):
result[data_field_value.datafield.label] = data_field_value.value
return result
def get_store(self, obj):
request = self.context['request']
store = request.query_params.get('store', '')
return store
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
So if I have the following model class in Django:
class Person(models.Model):
name = models.CharField(max_length=250)
is_active = models.BooleanField(default=True)
address_line1 = models.CharField(max_length=100, blank=True, null=True)
address_line2 = models.CharField(max_length=100, blank=True, null=True)
town = models.CharField(max_length=100, blank=True, null=True)
county = models.CharField(max_length=100, blank=True, null=True)
post_code = models.CharField(max_length=100, blank=True, null=True)
and my goal is to serialize it into the following JSON:
{
"name": "Joe Bloggs",
"is_active": true,
"contact": {
"address1": "Bloggs House"
"address2": "1 Bloggs Lane",
"city": "Bloggs Town",
"county": "Bloggs Town",
"zip": "BL0 GG5",
"country": "UK"
}
}
I tried the following, but it didn't work and I'm pretty sure that's not how the serializers.ListField is meant to work (I think it's meant to be for a list of the same thing):
class MailChimpListSerializer(serializers.ModelSerializer):
class Meta:
model = Person
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
city=serializers.CharField(source='town'),
state=serializers.CharField(source='town', read_only=True),
zip=serializers.CharField(source='post_code'),
country=serializers.SerializerMethodField()
)
permission_reminder = serializers.SerializerMethodField()
campaign_defaults = serializers.DictField(
from_name=serializers.CharField(source='name'),
from_email=serializers.CharField(source='primary_contact_email'),
subject=serializers.CharField(),
language=serializers.CharField()
)
email_type_option = serializers.SerializerMethodField()
fields = ('name', 'contact', 'permission_reminder',
'campaign_defaults', 'email_type_option')
How do I create the contact JSON list with the address etc in it?
What you want is a DictField not a ListField, the key contact in your desired JSON output is an object (dict in Python), not a list:
contact = serializers.DictField(
address1=serializers.CharField(source='address_line1'),
address2=serializers.CharField(source='address_line2'),
...
)
Here's another way that is more manual:
class MySerializer(serializers.ModelSerializer):
contact = serializers.SerializerMethodField()
def get_contact(self, obj):
return dict(
address1=obj.address1, # As long as the fields are auto serializable to JSON
some_field=SomeSerializer(obj.some_field).data,
)