class Product(models.Model):
name = models.CharField(max_length=80)
product_image = models.ImageField(upload_to='product/product/images/%Y/%m/%d/', blank=True)
price = models.IntegerField()
class Cart(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class CartItem(models.Model):
item = models.ForeignKey(Product, null=True, on_delete=models.CASCADE)
qty = models.IntegerField(default=1)
cart = models.ForeignKey(Cart, null=True, on_delete=models.CASCADE)
I'm trying to get an automatic total price that will be shown on check out page. I want to add a 'total_price' column on CartItem model and set the default 'item.price * qty', but when I tried to add this line to the class:
total_price = models.IntegerField(default=item.price)
since default value for qty is 1 but I got AttributeError: 'ForeignKey' object has no attribute 'price' error.
I also tried add this to the class:
#property
def total_price(self):
item = self.object.get(product=self.item)
return self.item.price
but I'm not sure which model will have the property? And when I added this method, I lost total_price column which I set its default as 0. I apologize for the lacking quality of solutions!
You are in right direction. You can try annotation or aggregation to get the total price. Here is one approach:
For all Cart Items using annotation with Sum:
Cart.objects.all().annotate(total_spent=Sum(
F('cartitem__item__price') *
F('cartitem__qty'),
output_field=models.FloatField()
))
For one Cart, you can try like this with aggregation:
class Cart(...):
....
#property
def total_price(self):
return self.cartitem_set.aggregate(price=Sum(
F('item__price') *
F('qty'),
output_field=models.FloatField()
)['price']
Change the total_price property to:
class CartItem(models.Model):
cart = models.ForeignKey(Cart, null=True, on_delete=models.CASCADE,
related_name="orders")
#property
def total_price(self):
return self.qty * self.item.price
And you can easily get the total price of the Order Item.
If you want to get Total amount of all CartItems prices can do like below:
class Cart(models.Model):
#property
def total_amount(self):
self.orders.annotate(total_spent=Sum(
F('item__price') *
F('qty'),
output_field=models.FloatField()
))
Related
I have a question here, I have two tables in my django models one for listings and one for bids
class Listing(models.Model):
class Meta:
verbose_name_plural = 'Listing'
title = models.CharField(max_length=64)
description = models.TextField()
price = models.DecimalField(max_digits=5, decimal_places=2)
image = models.URLField(max_length=500, default='')
category = models.CharField(max_length=32)
created = models.CharField(max_length=32)
addedon = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
def __str__(self):
return self.title
class Bid(models.Model):
class Meta:
verbose_name_plural = 'Bid'
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Listing, on_delete=models.CASCADE)
bid = models.DecimalField(max_digits=5, decimal_places=2)
created = models.DateTimeField(auto_now_add=True)
what i want is to show a page with all the user won items
def won(request):
listing = Listing.objects.filter(active=False)
the question here how i can make list of all then check max bid of each listing, then check is the current user is the winner and display it in the template won.html
like :
getMaxBid = Bid.objects.filter(item_id=listing.id).aggregate(Max('bid'))
maxBid = getMaxBid['bid__max']
then if the user is the winner display it
return render(request, "auctions/won.html", {
'listing': listing, 'active': False
})
thanks in advance
You can filter the Listing by first creating a Subquery expression [Django-doc] that will obtain the winner, and then check if the logged in user (or another user) is the winner, so:
from django.db.models import OuterRef, Subquery
listing = Listing.objects.alias(
bid_winner=Subquery(
Bid.objects.filter(item=OuterRef('pk')).order_by('-bid').values('user')
)[:1]
).filter(
active=False,
bid_winner=request.user
)
or Listings where the user has an active bid:
Listing.objects.filter(
bid__user=request.user,
active=True
).distinct()
How can i get "total" price of items of OrderItem in cart model from these models down below? I tried doing something in views but I get attribute error that QuerySet' object has no attribute 'total'.
views.py
def cart(request):
cart = Cart.objects.filter(order_user=request.user)
order_items = OrderItem.objects.filter(cart__in=cart)
total = 0
for i in order_items:
total = i.quantity * i.item.price + cart.total
cart.update(total=total)
models.py
class OrderItem(models.Model):
cart = models.ForeignKey('Cart', on_delete=CASCADE, null=True)
item = models.ForeignKey(Item, on_delete=CASCADE, null=True)
quantity = models.IntegerField(default=1)
class Item(Visits, models.Model):
title = models.CharField(max_length=150)
price = models.IntegerField(default=1000)
image = models.ImageField(upload_to='pictures', default='static/images/man.png')
description = models.TextField(default="Item")
visits = models.IntegerField(default=0)
class Cart(models.Model):
order_user = models.OneToOneField(User, on_delete=CASCADE)
ordered = models.BooleanField(default=False)
total = models.IntegerField(default=0, help_text="100 = 1EUR")
order_items = models.ManyToManyField(Item, related_name='carts', through=OrderItem )
Just aggregate the total of ModelField total of the queryset like so
Total = Cart.objects.all().aggregate('total')
# Filtered in your case
Total = Cart.objects.filter(order_user=request.user).aggregate('total')
Apply filtering as necessary.
Also I suggest to have a good read here
You can retrieve the cart information for current users via the OrderItem model itself.
Check how annotate works
from django.db.models import Count
order_items = (OrderItem.objects.filter(cart__order_user=request.user)
.annotate(total=Count("quantity")*(item__price) + cart__total)
)
Please note: Similar questions didn't help me as they have the category-foreignkey in the same class.
I have a simple Invoice app with models Invoice, Position, Product and Category. The Product is bound to the Category.
My target is to create a queryset that
filters e. g. a specific date-range
and then group all categories and build their sums
Here is a screenshot of the invoice respectively of its positions:
The expected result of the grouped query should look like this:
Can you help me to create a query that groups and sums the categories within the filtered date-range?
The only solution I was able to create was the filter of a specific date-range:
queryset = Position.objects.filter(invoice__date_of_purchase__range=['2019-01-01', '2019-12-31'])
models.py (which I have simplified):
from django.db import models
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=120)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
def __str__(self):
return self.name
class Invoice(models.Model):
invoice_code = models.CharField(max_length=15)
date_of_purchase = models.DateField()
customer_name = models.CharField(max_length=100)
def __str__(self):
return self.invoice_code
class Position(models.Model):
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.DecimalField(decimal_places=2, max_digits=6)
price = models.DecimalField(decimal_places=2, max_digits=8)
total = models.DecimalField(
decimal_places=2, max_digits=8, blank=True, null=True) # is calculated in view
def __str__(self):
return self.product.name
The following filter will return all categories that have an invoice in the date range and will also filter the annotation to sum only those positions for those invoices
categories = Category.objects.filter(
products__position__invoice__date_of_purchase__range=['2019-11-17', '2019-12-31']
).annotate(
sum=Sum('products__position__total')
)
Each category will now be annotated with an attribute "sum"
for category in categories:
print(category, category.sum)
I'm going to suggest a tweak based on my experience.
Put position into the invoice model as a many to many fields. This should make it cheaper to filter the date range of invoices. It also may help to add a "sent" bol field depending on your use case.
Either in your view or in a utils.py file. Loop thru the query set's "Position's" field with the category as the conditional to separate by category and += the Position.total field to your awaiting variable.
In my django shop I have a adding to cart function. But if I add the same product 2 times to the cart with a different quantity, 2 different objects are created. What's wrong with my code?
here is my view
def add_to_cart_view(request):
cart = getting_or_creating_cart(request)
product_slug = request.POST.get('product_slug')
product = Product.objects.get(slug=product_slug)
if request.method == "POST":
form = CartAddProductForm(request.POST or None)
if form.is_valid():
quantity = form.cleaned_data['quantity']
new_item, created = CartItem.objects.get_or_create(
product=product,
item_cost=product.price,
quantity=quantity,
all_items_cost=product.price*quantity,
)
if new_item.product.title == product.title:
cart.items.add(new_item)
cart.save()
if not created:
new_item.quantity += quantity
new_item.save(force_update=True)
cart.save()
new_cart_total = 0.00
for item in cart.items.all():
new_cart_total += float(item.all_items_cost)
cart.cart_total_cost = new_cart_total
cart.save()
return JsonResponse({
'cart_total': cart.items.count()
})
And here is my models
class CartItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
quantity = models.PositiveIntegerField(null=True, default=1)
item_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
all_items_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
def __str__(self):
return str(self.product.title)
class Cart(models.Model):
items = models.ManyToManyField(CartItem, blank=True)
cart_total_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
def __str__(self):
return str(self.id)
Thanks for any help!
#dirkgroten provided a very good answer, you can also use unique_together option to prevent creating a duplicate entry
suppose, you've three fields name, size, brand in the Product model
and you don't want to create any new entry with the same name, size and brand
You can set it like
class Product:
name = CharField(....)
size = CharField(....)
brand = CharField(....)
field4 = CharField(....)
class Meta:
unique_together = ("name","size","brand")
I personally do not entertain the use of unique_together, but it'll surely prevent from creating multiple entries in these kinds of situations from DB definition side, but you've to handle the same in the code too
The get_or_create function will try to fetch a CartItem with all the exact properties you pass it. In your case you're trying to match against product, item_cost, quantity and all_items_cost. If you pass it the same product with a different quantity, it won't match, it'll create a new CartItem.
Look at the documentation of get_or_create. Use only product for the query and defaults for setting the value when creating a new CartItem:
new_item, created = CartItem.objects.get_or_create(
product=product,
defaults = dict(
item_cost=product.price,
quantity=quantity,
all_items_cost=product.price*quantity),
)
I have two classes:
class Order(models.Model):
...
date = models.DateTimeField(blank=True, verbose_name=u'Date add',default=datetime.now)
price = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=u'Price', blank=True, null=True)
...
def __unicode__(self):
return "%s" % (self.date)
class OrderItem(models.Model):
...
date = models.DateTimeField(blank=True, verbose_name=u'Date add',default=datetime.now)
order = models.ForeignKey(Order, verbose_name=u'Order')
itemname = models.CharField(max_length=255, verbose_name=u'Item name')
quantity = models.PositiveIntegerField(default=1, verbose_name=u'Quantity')
price = models.DecimalField(max_digits=7, decimal_places=2, verbose_name=u'Price')
def __unicode__(self):
return "%s" % (self.itemname)
And I want to display orders with orderitems in list:
class OrderAdmin(admin.ModelAdmin):
list_display = ('price','<????>ORDERITEMS</????>')
How to do it?
It is a bit hard to do with your setup. If you use a related_name in your OrderItem model such as
order = models.ForeignKey(Order, related_name='items')
You could use it as a reference from the order to items. But again you have a OneToMany relationship so order have many items. You could crate a property in order to get you something like number_of_items such as
#property
def number_of_items(self):
return self.items.count()
and use that in the OrderAdmin such as
class OrderAdmin(admin.ModelAdmin):
list_display = ('price','number_of_items')
It is much easier if you are trying to access Order from OrderItem ModelAdmin because that returns one object so you could do:
class OrderItemAdmin(admin.ModelAdmin):
list_display = ('itemname',order__price')
note the use of double underscore between order and price.
I write this function to Order model:
def get_items(self):
text = ""
for i in self.oitems.all():
text = text + '<br />' + i.itemname
return text
get_items.allow_tags = True
And I add related_name="oitems" to Order Key in OrderItem. And it works.