Django reduce amount of queries (M2M relation with through model) - python-3.x

I would like to reduce the amount of similar queries. Here are my models:
class Skill(models.Model):
name = models.TextField()
class Employee(models.Model):
firstname = models.TextField()
skills = models.ManyToManyField(Skill, through='SkillStatus')
def skills_percentage(self):
completed = 0
total = 0
for skill in self.skills.all().prefetch_related("skillstatus_set__employee"):
for item in skill.skillstatus_set.all():
if item.employee.firstname == self.firstname:
total += 1
if item.status:
completed += 1
try:
percentage = round((completed / total * 100), 2)
except ZeroDivisionError:
percentage = 0.0
return f"{percentage} %"
class SkillStatus(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
My main problen is related to method skills_percentage, I make too many queries while calculating mentioned value. I have already improved situation a little bit with prefetch_related, but there are still extra queries in Django Debug Toolbar. What else can be done here?
I have tried to play with different combinations of select_related and prefetch_related. I thought about other options to calculate skills_percentage but they also required to many queries...
Thanks in advance.

You can try like this:
from django.db.models import Count, When, Case, Cast, FloatField
employees = Employee.objects.annotate(
total=Count('skills',distinct=True),
completed = Count('skills', filter=Q(skillstatus__status=True),distinct=True)
).annotate(
percentage= Case(
When(total=0, then=0.0),
default=(Cast(
F('completed')*100 / F('total'),
output_field=FloatField()
)
)
)
)
# usage
for employee in employees:
print(employee.percentage)
# or
employees.values('firstname', 'percentage')
Here I am counting skills twice, once without any condition for total and second time with a filter condition. Then I am annotating the division of completed/total value with the original queryset by casting it as FloatField.

You can use aggregation functions provided by Django's ORM. This can help to reduce the number of queries required to calculate the completed and total skills counts for an employee.
Using F() expressions and annotate() allows us to perform the calculation in a single query, without the need for a separate loop over the related Skill objects.
from django.db.models import Count, F
class Employee(models.Model):
...
def skills_percentage(self):
counts = self.skills.annotate(
completed_count=Count('skillstatus', filter=Q(skillstatus__status=True, skillstatus__employee=self)),
total_count=Count('skillstatus', filter=Q(skillstatus__employee=self)),
).aggregate(
completed_count_sum=Sum('completed_count'),
total_count_sum=Sum('total_count'),
)
completed = counts['completed_count_sum'] or 0
total = counts['total_count_sum'] or 0
try:
percentage = round((completed / total * 100), 2)
except ZeroDivisionError:
percentage = 0.0
return f"{percentage} %"

Related

Aggregate Sum shows total for all members

When i add aggregate function on my get_context_data it shows the total for all members and not according to there ID. Thank You
ItemListView
class ItemListView(ListView):
model = HBTYItem
template_name = "accounts/modals/nomodal/todo_list.html"
paginate_by = 2
ordering = ['id']
def get_queryset(self):
return HBTYItem.objects.filter(hbty_cust_id=self.kwargs["list_id"])
def get_context_data(self):
context = super().get_context_data()
context['t_sum'] = HBTYItem.objects.aggregate(Sum('price'))
context["hbty_list"] = HBTYList.objects.get(id=self.kwargs["list_id"])
return context
If you have user filed in HBTVItem you can use:
HBTYItem.objects.filter(user=self.request.user).aggregate(Sum('price'))
Or you can apply filter on any field you want it

How to automatically update model field if there are changes to the previous field?

I am trying to find a way to update the latest field in a model if there are changes in the earlier field.
for example: say I have a Cash Balance model
If I change the sale inv 134 debits to 50. how do I make this change reflect on the other cash balance coming after it automatically?
Edit 1 :
class AccountsModel(models.Model):
Date = models.DateField(auto_now=True)
INV_No = models.IntegerField(null=True,blank=True)
Discription = models.CharField(max_length=500,blank=True)
Debit = models.DecimalField(max_digits=8,decimal_places=2,default=0.0)
Credit = models.DecimalField(max_digits=8,decimal_places=2,default=0.0)
CashBalance = models.DecimalField(max_digits=8,decimal_places=2,default=0.0)
created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
#property
def GetLastCB(self):
return AccountsModel.objects.last().CashBalance
def get_bookno(self):
if not self.INV_No:
return ''
return self.INV_No
This is a simple model that I made. I made API views to GET, POST AND DELETE
What you probably want to use is a signal
https://docs.djangoproject.com/en/4.0/topics/signals/
from django.dispatch import receiver
#receiver(post_save, sender=CashBalance)
def update_cash_balance(sender, instance, **kwargs):
if instance.balance != instance.calculate_balance()
instance.set_balance()
Be wary of using .save() within the signal because you can start a recursive loop, put an if-statement check to prevent this from happening

Bank Account Sub classes OOP

B)The BankAccount class should then have two child classes, named SavingsAccount and NonTaxFilerAccount. The SavingsAccount class should have a ZakatDeduction( )function which deducts an amount of 2.5% of the current account balancewhen called. ο‚·The NonTaxFilerAccount class should have the withdraw function from the parent class overwritten. Where a 2% withholding tax is deducted from the account every timea withdrawal is made.
i did the first part but not getting the second one it keeps giving me attribute error
class BankAccount:
def __init__(self, init_bal):
"""Creates an account with the given balance."""
self.init_bal = init_bal
self.account = init_bal
def deposit(self, amount):
"""Deposits the amount into the account."""
self.amount = amount
self.account += amount
def withdraw(self, amount):
self.account -= amount
def balance(self):
print (self.account)
class SavingsAccount(BankAccount) :
def ZakatDeduction(self,amount):
self.account=amount*0.25
print(self.account)
class NonTaxFilerAccount(BankAccount):
def withdraw(self, amount):
self.account -= amount*(0.2)
print(self.account)
x = BankAccount(700)
x.balance()
y=BankAccount
y.SavingsAccount(700)
z=BankAccount
z.withdraw(70)
I think you have several problems. Basically your implementation of the BankAccount, SavingsAccount, and NonTaxFilerAccount classes are structurally correct. However:
Since the instructions say to reduce the account balance by 2.5% every time the ZakatDeduction is called, you should update the method to remove the amount as follows:
def ZakatDeduction(self):
self.account -= self.account*0.025
print(self.account)
Since the instructions say to reduce the account balance by an additional 2% of the withdrawn amount when a NonTaxFiler makes a withdraw3al you should update the NonTaxFiler withdraw method as follows:
def withdraw(self, amount):
self.account -= amount*(1.02)
print(self.account)
Employing these classes to create separate accounte with 700 balance should be as follows:
BA = BankAccount(700) #Create a base account
SA = SavingAccount(700) #Create a Savings Account
NTF = NonTaxFiler(700) #Create a NonTaxFilerAccount
performing the following then yields:
BA.withdraw(25)
BA.balance()
675
SA = SavingsAccount(700)
SA.ZakatDeduction()
682.5
NTF = NonTaxFilerAccount(700)
NTF.withdraw(25)
674.5
The attribute error is correct, and in fact its message tells you the issue. Your classes are ok. The error is in the usage. You have:
y=BankAccount
y.SavingsAccount(700)
This means that the y variable now refers to the BankAccount class. The next line attempts to call y.SavingsAccount, and the BankAccount class does not have a method called SavingsAccount.
Did you perhaps mean:
y = SavingsAccount(700)
Note that python is whitespace specific. While technically valid, for readability, you should use the same level of indentation everywhere, but some of your methods are indented by 4, while others by 3

I have a problem with classes and inputs in Python

I just started learning about classes and am trying to build a calculator
that tells people how much they need to tip the waiter as a small project
but instead of entering myself the information i want that a user will do it himself so it will suit hes needs.
now i think i built it right, the computer accepts the inputs but when i try to call
the return_answer() function i get an error that says:
AttributeError: tip_calculator object has no attribute 'return_answer'
can someone please explain to me what i am doing wrong and how to fix it?
thanks in advance :)
class tip_calculator:
def __init__(self,bill,amount_of_diners,precent):
self.bill = bill
self.amount_of_diners = amount_of_diners
self.precent = precent
def return_answer(self):
print("The amount you need to pay is: ", precent * bill / amount_of_diners ** bill * 1000)
calc = tip_calculator(int(input("What was the bill?: ")),
int(input("With how many people did you eat?: ")),
int(input("what '%' do you want to give the waiter?: ")))
calc.return_answer()
The problem is with the indentation:
class tip_calculator:
def __init__(self, bill, amount_of_diners, precent):
self.bill = bill
self.amount_of_diners = amount_of_diners
self.precent = precent
def return_answer(self):
print(
"The amount you need to pay is: ",
self.precent * self.bill / self.amount_of_diners ** self.bill * 1000,
)
calc = tip_calculator(
int(input("What was the bill?: ")),
int(input("With how many people did you eat?: ")),
int(input("what '%' do you want to give the waiter?: ")),
)
calc.return_answer()
Your return_answer method was indented wrongly inside the initialiser (__init__). Also, in your return_answer, when you want to access class members like the fields bill, amount_of_diners and percent you need to do it with self because they are members of your class and not local variables to your method.

verbose if condition " if not hasattr(self, '__total'):"

I am following "Fluent Python" to learn Function and Design Pattern:
In chapter 6 example-code/strategy.py
class Order: # the Context
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self) # <1>
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
I am very confused about:
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
What's the purpose of if condition here? I guess it could be more readable if:
def total(self):
return sum(item.total() for item in self.cart)
What's the key point I missed? could you please provide any hints?
What happens if you call total more than once? If self.cart hasn't changed, then you're needlessly recalculating the total, a potentially expensive operation.
You're checking if you've already stored the value. If you haven't you calculate it, but if you have you simply return the stored value without recalculating.
As an aside, I would expect name mangling to make your life difficult here because of the double underscore at the beginning of __total. You may want to consider switching to a single underscore.

Resources