Django sum on an external attribut - python

I have an issue with database and sum in Django.
I have 3 tables: customer, order and orderLine.
For a report, I would like to calculate the sum of all line price for every order of a customer.
class Customer(models.Model):
firstName = models.CharField(max_length=200)
lastName = models.CharField(max_length=200)
mail = models.EmailField(max_length=100)
etc...
def get_count_of_orders(self):
return self.orders.count()
def get_sum_line_prince_of_all_orders(self):
???????????
return (sum_of_all_line_prince_all_orders)
class Order(models.Model):
orderNum = models.CharField(max_length=200)
customer = models.ForeignKey(Customer, related_name="orders")
globalAmount = models.DecimalField(max_digits=20, decimal_places=4)
...
class OrderLine(models.Model):
order = models.ForeignKey(Order, related_name="ordersLines")
linePrice = models.DecimalField(max_digits=20, decimal_places=4)
...
I don't know what to set in get_sum_of_orders to get the right result.
I've trayed different things like annotate or aggregate.
But without success at the moment.
I didn't understand this process at the moment.
Could you help me?

You can access all orders with:
self.orders.all()
and you can iterate them with:
sum = 0
for each_order in self.orders.all():
sum += each_order.globalAmount
return sum
Here I presume that globalAmount is the amount you need to calculate.
If you need to reach OrderLine -> linePrice through each customer and calculate the sum of the linePrice(s) of each customer, try the following:
for each_order in self.orders.all(): #each order
for each_OrderLine in each_order.ordersLines.all()
sum += each_OrderLine.linePrice
Or you could use list comprehension:
sum([myorder.linePrice for myorder in order.ordersLines.all() for order in self.orders.all()])

Related

How to model many-to-many database with 3 tables

I'm working on a django backend and I'm trying to model a database and want to do it the best practice way.
I need a "User" table, a "Portfolios" table and a "Stocks" table. A user can have multiple portfolios which consist of multiple stocks.
This is my code so far:
class User(models.Model):
user_id = models.AutoField(primary_key=True)
username = models.CharField(max_length=25)
in_cash = models.DecimalField(max_digits=15, decimal_places=2)
class Portfolios(models.Model):
portfolio_id = models.AutoField(primary_key=True)
user_id = models.ForeignKey("User", on_delete=models.CASCADE)
stock_id = models.ForeignKey("Stocks", on_delete=models.CASCADE)
buy_datetime = models.DateTimeField(default=datetime.now, blank=True)
number_of_shares = models.IntegerField()
class Stocks(models.Model):
stock_id = models.AutoField(primary_key=True)
stock_symbol = models.CharField(max_length=12)
In my head I would have an entry in the "Portfolios" table for each stock of a portfolio.
So "Portfolios" would look like
portfolioid 1, userid: 1, stockid: 1, buydate: 12.01.2019, shares: 20
portfolioid 1, userid: 1, stockid: 2, buydate: 19.02.2020, shares: 41
So there is one portfolio, which contains two stocks. But overall that doesn't seem right. If used like in my example above I can't have the portfolioid as a primary key, how can I improve this?
Thanks for your time
What confused me is the name portfolio, which I would call position. Your initial code was correct, although I changed it a bit, removing AutoField which is probably not needed, using a OneToOneField to connect a Customer to a User, removing the s at the end of class names, which are templates, and therefore should be singular, nor plural, adding price to the Stock. And finally changing Portfolio, which should be the sum of all the Positions.
from django.conf import settings
class Customer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
in_cash = models.DecimalField(max_digits=15, decimal_places=2)
def __str__(self):
return self.customer.username
class Position(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
stock = models.ForeignKey('Stock', on_delete=models.CASCADE)
number_of_shares = models.IntegerField()
buy_datetime = models.DateTimeField(default=datetime.now, blank=True)
def __str__(self):
return self.customer.customer.username + ": " + str(self.number_of_shares) + " shares of " + self.stock.stock_symbol
class Stock(models.Model):
stock_symbol = models.CharField(max_length=12)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.stock_symbol
In my head I would have an entry in the "Portfolios" table for each
stock of a portfolio. So "Portfolios" would look like
portfolioid 1, userid: 1, stockid: 1, buydate: 12.01.2019, shares: 20
portfolioid 1, userid: 1, stockid: 2, buydate: 19.02.2020, shares: 41
So there is one portfolio, which contains two stocks. But overall that
doesn't seem right. If used like in my example above I can't have the
portfolioid as a primary key, how can I improve this?
You are correct, except that should be applied to a Position, each of which is unique, not the Portfolio, which is all the Positions the Customer has.
As in usual many-to-many cases, you will need to create an intermediary table which is also called junction table/association table. Associative Entity
Your users are going to have several portfolios:
class UserPortfolio(models.Model):
user_id = models.ForeignKey("User")
portfolio_id = models.ForeignKey("Portfolio")
The portfolios will have multiple stocks in them:
class PortfolioStock(models.Model):
portfolio_id = models.ForeignKey("Portfolio")
stock_id = models.ForeignKey("Stock")
Now a user can have several portfolios, and those portfolios will include several stocks. In order to get access to the corresponding stocks for a user, you will need to join the tables.

How to write Django query to grab all objects in descending order according to two number fields?

I'm using Django. i'm trying to write query according to the top rated products. i have product table. as you can see below.
class Product(models.Model):
user = models.ForeignKey(User, verbose_name=_("Owner"), on_delete=models.CASCADE)
name = models.CharField(_("Name"), max_length=150,null=True)
average_rating =models.DecimalField(_("average rating"), max_digits=10, decimal_places=2,null=True,blank=True)
total_reviews = models.IntegerField(_("total reviews "),default=0,null=True,blank=True)
is_remove = models.BooleanField(_("Remove"), default=False)
create_time = models.DateTimeField(_("Create time"), default=timezone.now)
Now i want to get all objects which have highest average rating and total count.
I have tried many things below. but none of them worked.
1 -
def get_all_top_rated_products(self):
query = self.filter(is_remove=False).order_by("total_reviews","average_rating")
print(query)
return query
2
def get_all_top_rated_products(self):
query = self.filter(is_remove=False).aggregate(Max('average_rating'),Max('total_reviews'))
print(query)
return query
You should order in descending order, you can do this by prefixing the fieldname with a minus (-):
def get_all_top_rated_products(self):
return self.filter(is_remove=False).order_by(
'-average_rating', '-total_reviews'
)

How to filter highest amount for each single date in Django?

Models:
Customer Table
class Customer(models.Model):
name = models.CharField(max_length=100)
email = models.CharField(max_length=30)
phone = models.CharField(max_length=12)
def __str__(self):
return f"Customer : {self.name}"
Order Table
class Orders(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
total_amount = models.PositiveIntegerField()
def __str__(self):
return f"Customer: {self.customer.name} | Total Amount: {self.total_amount}"
Query I am using:
order = Orders.objects.values('customer__id', 'customer__name', 'customer__email',
'customer__phone').annotate(Sum('total_amount')).order_by('order_date')
This is the result that I am receiving from the whole record:
The result that I needed should be The highest total amount from each date/day. This means it should return the highest total amount per date/day and each day may contain a different number of records. For example, if we need 5 days record and each day has 10 entries (assume), then it should give us only the five highest total amount orders from five days(one record per day of the highest total amount order). hope you got my question( i tried my best to explain).

Save a ForeignKey "child" without saving the "parent" first?

Suppose I have a view for saving an order to a database based on cart contents:
def cart_checkout(request):
order = Order()
order.first_name = 'x'
order.last_name = 'y'
order.address = 'z'
order.save()
cart = Cart(request)
for product_id, product_quantity in cart:
product = Product.objects.get(pk=product_id)
order_item = OrderItem()
order_item.order = order
order_item.name = product.name
order_item.price = product.price
order_item.amount = product_quantity
order_item.save()
order.update_total_price() # updates the Order total price field with the sum of order items prices
order.save()
return HttpResponse('Checked-out!')
As you can see, I am calling order.save() twice in this view: first to create an Order instance the OrderItems can be attached to in the for loop, and then to update the total price of the order based on order items in it. If I removed the first .save(), I would get an error on the second one telling me the order needs to be saved first.
Calling the .save() method twice does not seem DRY enough to me. Is there a way to do it only once?
Note that I am not subclassing ModelForm, so I cannot use .save(commit=False). Also, I do not want to just hide the save() method in the update_total_price() method.
Models.py:
from django.db import models
from .mixins import DisplayNameMixin
class Product(DisplayNameMixin, models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def update_total_price(self):
order_items = self.orderitem_set.all()
self.total_price = sum([
x.price * x.amount
for x in order_items
])
class OrderItem(models.Model):
order = models.ForeignKey('Order', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
I think, you can't help but save the order twice, as you need to have an order_id to create the OrderItems, and then update the order with the items' amount.
I have a few suggestions to make though.
You can make total_price a calculated property, so that you would not have to save the order:
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
#property
def total_price(self):
return sum([
x.price * x.amount
for x in self.orderitem_set.all()
])
From DB theory perspective your DB structure is wrong. It needs to be normalized first.
Why it is wrong?
Order.total_price is redundant table column. That information can be found with aggregation. At DB level there are no protections preventing DB users (Django app in your case) from entering compromised data. So your DB can be telling two different total prices (Order.total_price != SUM(OrderItem.price * OrderItem.amount)) at the same time.
So to appease DB normalization gods you need to drop total_price field and use Django aggregations/annotations (https://docs.djangoproject.com/en/3.0/topics/db/aggregation/) when you need to access it.
Saying that, there could be a totally valid reason to put total_price inside Order table. That reason usually is performance. Sometimes SQL query complexity (It is very annoying to filter by an aggregated column).
But there is a price. And that price is de-normalization of your DB. In your case you are paying breaking DRY principle.
Just make sure that you are calling both save()'s in the same transaction.
To expand on petraszd's answer (i.e. remove the total_price field) and engin_ipek's answer (i.e. add total_price as a calculated property), you could try making total_price a cached property, to avoid calculating the same value more than once - as long as the same Order instance is passed around.
You would also probably make the calculation a little less expensive if you used aggregation to calculate the total price, as petraszd mentioned, e.g. adding the products of price and amount.

Django - Cannot resolve keyword 'total' into field

Am having a model called Cart which contains the following fields
class Cart(models.Model):
client = models.ForeignKey(User, null=True)
description = models.CharField(max_length = 100)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField()
ordered = models.BooleanField(default=False)
created_on = models.DateTimeField(auto_now_add = True)
def __str__(self):
return self.description
def total(self):
return self.price * self.quantity
I wish to get the total amount per item in django views.
Here is the cart views
def cart(request): # Client View
request_user = request.user
item = Cart.objects.filter(client=request_user, ordered=False).values('total')
print "item ", item
If i print item, i get
Cannot resolve keyword 'total' into field. Choices are: client, client_id, created_on, description, docfile, id, order_id, ordered, price, quantity
But if i print item when .value('price'), i get result.
Is there a way to get value of total amount
Why don't you try this? ie, access the total method through Cart instance.
[i.total() for i in Cart.objects.filter(client=request_user, ordered=False)]
You need to create total function which will take price of each object you filter on the basis of client OR you can right query as follows:
total = 0
for i in Cart.objects.filter(client=request_user, ordered=False).all():
#print i.price
total = total+i.price
You can't write values('total'), as you don't have any field in your card model as total.

Categories