Django and use calculated values in QuerySet - python

I have a function to convert net to gross price like this.
taxRate is the tax value. e.g. 23, 8, 5, 0
def gross(netValue, taxRate: int, currencyPrecision=2):
if taxRate > 0:
return round(netValue * (100 + taxRate) / 100, currencyPrecision)
else:
return round(netValue, currencyPrecision)
In my model I have a class that is order items with fields: netPrice and taxId.
class OrderPosition(models.Model):
posNumber = models.AutoField(auto_created=True, primary_key=True, editable=False, blank=False)
order = models.ForeignKey(OrderHeader, on_delete=models.PROTECT)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
quantity = models.DecimalField(blank=False, max_digits=3, decimal_places=0)
netPrice = MoneyField(blank=False, max_digits=6, decimal_places=2, default=Money("0", "PLN"))
taxId = models.IntegerField(blank=False, default=0)
The value of the entire order with a given ID can be calculated as follows:
def getNetValue(self):
posList = OrderPosition.objects.filter(order_id=self.orderID)
if posList:
return str(posList.aggregate(netValue=Sum(F('quantity') * F('netPrice'),
output_field=MoneyField()))['netValue'])
else:
return "0"
Question: Can I use my function "gross()" in a query to calculate the gross value of an order in a similar way to calculate the net worth?
I suppose the aggregation function is performed on the database side (mySQL in my case) and we can only use what is understandable at the database and SQL level.

Try this:
net_value = OrderPosition.objects.filter(order_id=self.order_id).aggregate(
net_value=Sum(F('quantity') * F('net_price'), output_field=MoneyField())
)['net_value']
tax_rate = OrderPosition.objects.filter(order_id=self.orderID) \
.values('taxId')[0]['taxId']
gross_value = gross(net_value, tax_rate)
we need to iterate over the order item and calculate the gross for each item like this:
total_gross_value = Money(0, "PLN")
for order_item in OrderPosition.objects.filter(order_id=self.order_id):
net_price = order_item.net_price
tax_rate = order_item.tax_id
gross_price = gross(net_price, tax_rate)
total_gross_value += gross_price
we can use annotate method on queryset like this:
from django.db.models import F, Func
class GrossValue(Func):
function = 'ROUND'
template = '%(function)s(%(expressions)s * (100 + %(tax_rate)s) / 100, 2)'
def __init__(self, expression, tax_rate, **extra):
super().__init__(expression, tax_rate=tax_rate, **extra)
OrderPosition.objects.filter(order_id=self.order_id).annotate(
gross_price=GrossValue(F('net_price'), F('tax_id'))
).aggregate(Sum('gross_price'))
have a nice day ;)

Related

python mongoengine EmbeddedDocumentListField and aggregate

I wanna now if MongoEngine aggregate() functionality only works on QuerySet?
well, this is the project I'm working on
#imports
class Status(Enum):
ACTIVE = "active"
CLOSED = "closed"
class OrderType(Enum):
BUY = "buy"
SELL = "sell"
class Order(EmbeddedDocument):
type = EnumField(OrderType, required=True)
vol = IntField(required=True)
time = DateTimeField(required=True, default=datetime.now())
class Stock(EmbeddedDocument):
name = StringField(max_length=20, required=True)
orders = EmbeddedDocumentListField(Order, required=True)
status = EnumField(Status, required=True, default=Status.ACTIVE)
amount = IntField(required=True, default=0)
pnl = FloatField()
class Portfolio(Document):
account = IntField(required=True, unique=True)
stocks = EmbeddedDocumentListField(Stock, required=True)
balance = FloatField()
equity = FloatField()
I want to be able to make Stock.amount field be an aggregation of Order.vol which, as can be seen, is an EmbeddedDocumentListField of stock. I already know about mongoengine.signals and seen examples on aggregate(), but all of them use aggregate() on QuerySet returned by .objects(). So what is the tweak for it? Many Thanks.
Currently I come up with this:
def handler(event):
def decorator(fn):
def apply(cls):
event.connect(fn, sender=cls)
return cls
fn.apply = apply
return fn
return decorator
#handler(signals.post_init)
def update_amount(sender, document):
orders = document.orders
amnt = 0
for order in orders:
if order.type == OrderType.Buy:
amnt += order.vol
else:
amnt -= order.vol
document.amount = amnt
and then I added decorator #update_amount.apply to my Stock class. but it would work nicer with aggregate if it's possible

Django multiple annotate with Sum get wrong answer

I'm trying to use .annotate() with multiple Sum() But i got wrong calculations.
I've read that I should use Subquery but I didn't get it done with it maybe I use it in wrong way (because it is first time) or it doesn't solve my issue.
#managers.py
class DonationQuerySet(QuerySet):
def completed(self):
return self.with_donations_stats().filter(
amount__lte=F('total_donation'), deleted_at=None)
def not_completed(self):
return self.with_donations_stats().filter(
amount__gt=F('total_donation'), deleted_at=None)
def with_donations_stats(self):
return self.annotate(
wallet_donation=Coalesce(Sum('wallet_transaction__amount'), 0),
normal_donation=Coalesce(Sum('transactions__amount'), 0),
total_donation=F('wallet_donation') + F('normal_donation'))
class DonationManager(Manager):
def get_queryset(self):
return DonationQuerySet(self.model, using=self._db)
def completed(self):
return self.get_queryset().completed()
def not_completed(self):
return self.get_queryset().not_completed()
def with_donations_stats(self):
return self.get_queryset().with_donations_stats()
#models.py
class Transaction(models.Model):
def __str__(self):
return self.payment_id + ' - ' + self.status
condition = models.ForeignKey('condition.Condition', related_name='transactions',
on_delete=models.CASCADE)
amount = models.IntegerField(null=False, blank=False)
class WalletTransaction(AbstractBaseModel):
condition = models.ForeignKey("condition.Condition", on_delete=models.SET_NULL, related_name='wallet_transaction', null=True)
amount = models.PositiveIntegerField()
def __str__(self):
return f"{self.id}"
class Condition(models.Model):
def __str__(self):
return str(self.id) # .zfill(10)
STATUS_PUBLISHED = "Published"
STATUS_CHOICES = (
(STATUS_PUBLISHED, 'Published'),)
status = models.CharField(max_length=25, choices=STATUS_CHOICES, db_index=True)
donations = DonationManager()
and in my view i was querying for published conditions
conditions =
Condition.donations.filter(status=Condition.STATUS_PUBLISHED).with_donations_stats().order_by(
'-id')
UPDATE
the final query is SELECT "conditions"."id", "conditions"."user_id", "conditions"."association_id", "conditions"."nid", "conditions"."amount", "conditions"."donation", "conditions"."nid_type", "conditions"."first_name", "conditions"."father_name", "conditions"."last_name", "conditions"."nationality", "conditions"."gender", "conditions"."mobile", "conditions"."region", "conditions"."city", "conditions"."district", "conditions"."dob", "conditions"."introduction_file", "conditions"."disease_validation_file", "conditions"."medical_report_file", "conditions"."treatment_plan_file", "conditions"."cost_file", "conditions"."case_report_file", "conditions"."invoices_file", "conditions"."payment_file", "conditions"."generated_pdf_file", "conditions"."recovery_report_file", "conditions"."report_date", "conditions"."issued_place", "conditions"."specialization", "conditions"."disease_type", "conditions"."action_type", "conditions"."case_type", "conditions"."treatment_entity", "conditions"."accommodation_type", "conditions"."family_members", "conditions"."income_avg", "conditions"."medical_evaluation_status", "conditions"."researcher_opinion", "conditions"."rejection_reason", "conditions"."justification", "conditions"."insurance_company_name_ar", "conditions"."insurance_company_name_en", "conditions"."insurance_company_id", "conditions"."insurance_beneficiary_number", "conditions"."insurance_beneficiary_type", "conditions"."insurance_class", "conditions"."insurance_expiry", "conditions"."insurance_limit", "conditions"."insurance_policy", "conditions"."status", "conditions"."created_at", "conditions"."updated_at", "conditions"."published_at", "conditions"."deleted_at", "conditions"."image", "conditions"."last_donation_at", COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "wallets_wallettransaction" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."id" ORDER BY U0."id" DESC LIMIT 1), 0) AS "wallet_donation", COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "transactions" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."condition_id" LIMIT 1), 0) AS "normal_donation", (COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "wallets_wallettransaction" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."id" ORDER BY U0."id" DESC LIMIT 1), 0) + COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "transactions" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."condition_id" LIMIT 1), 0)) AS "total_donation" FROM "conditions" WHERE "conditions"."status" = Published ORDER BY "conditions"."id" DESC
Note: and I'm using the same annotation in another view resulting the same thing.
You have stumbled upon the problem that Combining multiple aggregations [Django docs] will yield the wrong results because joins are used instead of subqueries.
Since to make aggregations over relations Django makes joins and with aggregation on multiple relations there would be multiple joins the results of course are wrong. Therefore we need to annotate using subqueries:
from django.db.models import OuterRef, Subquery
class DonationQuerySet(QuerySet):
...
def with_donations_stats(self):
# Subquery for wallet donation
# Added order_by because it appears you have some default ordering
wallet_donation = WalletTransaction.objects.filter(
condition=OuterRef('pk')
).order_by().values('condition').annotate(amount_sum=Sum('amount')).values('amount_sum')[:1]
# Subquery for normal donation
normal_donation = Transaction.objects.filter(
condition=OuterRef('pk')
).values('condition').annotate(amount_sum=Sum('amount')).values('amount_sum')[:1]
return self.annotate(
wallet_donation=Coalesce(Subquery(wallet_donation), 0),
normal_donation=Coalesce(Subquery(normal_donation), 0),
total_donation=F('wallet_donation') + F('normal_donation')
)

I want to average by date column Django?

I've been working on it for a week, but I couldn't.
This is what he wants to do:
I want to average the "fiyat" column relative to the "ay_yil" column, i.e .:
{ '2020-09-15': 67333.3, '2020-02-15': 29750,0 }
models.py:
class Araba(models.Model):
marka = models.CharField(max_length=25, verbose_name='Marka')
model = models.CharField(max_length=25, verbose_name='Model')
motor = models.CharField(max_length=25, verbose_name='Motor')
yil = models.PositiveSmallIntegerField(verbose_name='Yil')
km = models.PositiveIntegerField(verbose_name='Km')
fiyat = models.PositiveIntegerField(verbose_name='Fiyat')
ay_yil= models.DateField(verbose_name='Ay Yıl')
def __str__(self):
return self.marka
views.py:
def veri(request,marka,model,motor):
veri= Araba.objects.filter(marka=marka,model=model,motor=motor)["ay_yil"]
veri2=veri.aggregate(ort=Avg('fiyat'))
print(veri2)
return render(request,"veri.html")
dataset:
from django.db.models import Avg
def veri(request, marka, model, motor):
veri= Araba.objects.filter(marka=marka,model=model,motor=motor).values('ay_yil')
veri2=veri.annotate(Avg('fiyat'))
print(veri2)
return render(request,"veri.html")

How to filter on calculated model values using list comprehension

I have a model where I calculate totals
class Material(models.Model):
version = IntegerVersionField( )
code = models.CharField(max_length=30)
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=80, blank=True)
description = models.TextField(null=True, blank=True)
min_quantity = models.DecimalField(max_digits=19, decimal_places=10)
def _get_totalinventory(self):
from inventory.models import Inventory
return Inventory.objects.filter(warehouse_Bin__material_UOM__UOM__material=self.id, is_active = True).aggregate(Sum('quantity'))
total_inventory = property(_get_totalinventory)
def _get_totalpo(self):
from purchase.models import POmaterial
return POmaterial.objects.filter(material=self.id, is_active = True).aggregate(Sum('quantity'))
total_po = property(_get_totalpo)
def _get_totalso(self):
from sales.models import SOproduct
return SOproduct.objects.filter(product__material=self.id , is_active=True ).aggregate(Sum('quantity'))
total_so = property(_get_totalso)
So far so good , my calculate fields calculate correctly and are displayed in template without any problem
Since I can not filter directly by the property As suggested i am using python list comprehension
So this is what I wrote that does give me output and no errors .
po_list = [n for n in Material.objects.all()
if ((F('total_inventory') + F('total_po') - F('total_so')) < F('min_quantity'))]
However Current list comprehension doesn't filter correctly
I am getting output that should be filtered out. What i am doing wrong?
It is not filtering anything it is just returning the list a it is
I doubt you can call F on model class methods. You can instead call the methods directly from the objects in the list comprehension:
po_list = [n for n in Material.objects.all() if (n.total_inventory['quantity__sum']
+ n.total_po['quantity__sum']
- n.total_so['quantity__sum'])
< n.min_quantity]

Auto increament the invoice number in django backend for new invoice

I want to auto increament the invoice number which is 3 digits char and 4 digits number.
class Invoice:
invoice_no = models.CharField(max_length=500, null=True, blank=True, validators=[RegexValidator(regex='^[a-zA-Z0-9]*$',message='Invoice must be Alphanumeric',code='invalid_invoice number'),])
I register this model in backend. But now when i click on create invoice in admin the invoice should be auto filled. When i again click on create new invoice in admin, the invoice_number should be incremented by one and should be auto field.
Ex for Invoice number MAG0001, MAG0002, MAG0003 etc and this should be auto field in admin when i click on create new invoice.
Define a function to generate invoice number.
def increment_invoice_number():
last_invoice = Invoice.objects.all().order_by('id').last()
if not last_invoice:
return 'MAG0001'
invoice_no = last_invoice.invoice_no
invoice_int = int(invoice_no.split('MAG')[-1])
new_invoice_int = invoice_int + 1
new_invoice_no = 'MAG' + str(new_invoice_int)
return new_invoice_no
Now use this function as default value in your model filed.
invoice_no = models.CharField(max_length=500, default=increment_invoice_number, null=True, blank=True)
This is just an idea. Modify the function to match your preferred invoice number format.
In above arulmr answer just edit char field
def increment_invoice_number():
last_invoice = Invoice.objects.all().order_by('id').last()
if not last_invoice:
return 'MAG0001'
invoice_no = last_invoice.invoice_no
invoice_int = int(invoice_no.split('MAG')[-1])
width = 4
new_invoice_int = invoice_int + 1
formatted = (width - len(str(new_invoice_int))) * "0" + str(new_invoice_int)
new_invoice_no = 'MAG' + str(formatted)
return new_invoice_no
class Invoice(models.Model):
invoice_no = models.CharField(max_length = 500, default = increment_invoice_number, null = True, blank = True)
This will work fine.
def invoiceIncrement():
get_last_invoice_number
incremente_last_invoice_number
return next_invoice_number
class Invoice:
invoice_no = models.CharField(max_length=500, null=True, blank=True,
validators=[RegexValidator(regex='^[a-zA-Z0-9]*$',
message='Invoice must be Alphanumeric',code='invalid_invoice number'),],
default=invoiceIncrement)
Try this: there are some obvious issues:
if more than one person adds an invoice at the same time, could have collision
will need to make an extra db call each time you create a new invoice.
Also: you may want to just consider using either an auto_increment or UUID.
Maybe this code can help
def increment_invoice_number():
last_invoice = Invoice.objects.all().order_by('id').last()
if not last_invoice:
return 'MAG0001'
invoice_no = last_invoice.invoice_no
new_invoice_no = str(int(invoice_no[4:]) + 1)
new_invoice_no = invoice_no[0:-(len(new_invoice_no))] + new_invoice_no
return new_invoice_no
def invoice_number_gen():
last_invoice = Invoice.objects.all().order_by('id').last()
last_invoice_number = last_invoice.invoice_no
#invoice number format is 'customer_name_short + number' eg: CS003
last_invoice_digits =int(last_invoice_number[2:])
#comment: slicing CS003 to get the number 003 and converting to int.
last_invoice_initials = last_invoice_number[:2]
new_invoice_digits = last_invoice_digits + 1
new_invoice_number = last_invoice_initials + str(new_invoice_digits)
return (new_invoice_number)

Categories

Resources