Django conditional querysets - sql

Alright,
So I'm using a general listview showing all orders where the payment hasn't been completed yet. Let's call them outstanding orders, orders which still require some sort of payment.
Orders contain one or multiple items and can contain zero or more payments.
So I want to compare the total order value, compare this value with the total payment and if these or not equal to zero, show them in the list.
Is there someway I can build a new queryset for items which do not meet a certain condition?
views.py
class OutstandingOrderListView(ListView):
model = Order
def get_queryset(self):
queryset = Order.objects.all()[:5]
for record in queryset.iterator():
# Retrieve order_total
order_total = record.item_set.aggregate(total=Sum('price'))['total']
# Retrieve payment_total
payment_total = record.payment_set.aggregate(total=Sum('amount'))['total']
# Compare both
delta = order_total - payment_total
if delta != 0:
print("These guys owe you money!")
# Add record to new queryset?
models.py
class Order(models.Model):
no = models.CharField(max_length=9, default=increment_order_number,
editable=False, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL, null=True, related_name='created_by')
class Item(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
name = models.CharField(max_length=200, default="", blank=True)
price = models.DecimalField(max_digits=5,
decimal_places=2, default=Decimal('000.00'))
class Payment(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
date = models.DateField(default=now)
amount = models.DecimalField(max_digits=5, decimal_places=2,
default=Decimal('000.00'))
Tried to use filtering on annotations like #bdbd mentioned in the comments, in this case we could use exclude():
queryset = Order.objects.annotate(order_total=Sum('item__price')) \
.annotate(payment_total=Sum('payment__amount')) \
.exclude(order_total=F('payment_total'))[:5]
Even though this works, the second annotation shows very unusual results, not sure why but it looks like a multiple from order_total...

Related

Django annotate multiple aggregators over grouped values

Due to the structure of my project, I need to have multiple aggregations over three interlocked tables. With structure looking somewhat like this:
class ItemMeta(models.Model):
item = models.ForeignKey(
Item, on_delete=models.SET_NULL, null=True
)
class = models.CharField(max_length=2048, null=True, blank=True)
department = models.CharField(max_length=2048, null=True, blank=True)
class Item(models.Model):
amount = models.DecimalField(max_digits=18, decimal_places=2)
status = models.CharField(max_length=2048, null=True, blank=True, choices=ItemStatus.choices)
class CartItem(models.Model):
author = models.ForeignKey(to=UserModel, on_delete=model.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
class ItemStatus(models.TextChoices):
UNDER_CONSIDERATION = 'UNDER_CONSIDERATION', 'Under consideration'
IMPOSED = 'IMPOSED', 'Imposed'
PAID = 'PAID', 'Paid'
And I need to have item grouping by class, department and status both in cart and outside of it. I also need to have aggregations of combined amounts of items in different statuses, as well as counts of different items in cart and existing. So the structure of the response has to always contain 5 values: sum of paid, imposed and considered items, and count of items existing and in cart of the calling user. I inherited from last poor sod this piece of code to do these:
def _sum(self, status):
return Coalesce(Sum('amount', filter=Q(status=status)), 0.0, output_field=FloatField())
def annotate_kwargs(self):
return {
'under_consideration_amount': self._sum(ItemStatus.UNDER_CONSIDERATION),
'imposed_amount': self._sum(ItemStatus.IMPOSED),
'paid_amount': self._sum(ItemStatus.PAID),
'count': Count('pk', distinct=True),
'in_cart': Count('pk', distinct=True, filter=Q(cartitem__author=self.user)),
}
def get(self):
return self.queryset \
.values(*self.group_by) \
.annotate(**self.annotate_kwargs())
Which basically takes the Item queryset and groupes it according to request and then annotates it. Problem is, it returns lies, as is highlighted in the docs. Methinks having 3 different tables has something to do with it, but at this point i have no way to change the model structure, so it has to stay as it is or have as little change as possible. My question is how to have these aggregations? I tried using subquery, but i don't know how to make it work with .values clause

Django query set filter

I'm trying to create a query set that filters all the cars hired by a user.
the car hire model has a foreignkey which stores the user's ID when a car is hired
My current solution is like this, where I get current users ID and then try filtering the Cars database against the user's ID.
view.py:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(user_id__in=current_users_id)
args =
{
'car_hired': car_hired,
}
Models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.user.id)
class car_hired(models.Model):
car = models.ForeignKey('Car', on_delete=models.CASCADE)
customer = models.ForeignKey('UserProfile', on_delete=models.CASCADE)
start_date = models.DateField(help_text='Date of booking')
end_time = models.DateField(help_text='Date of booking')
def __str__(self):
return str(self.id)
However, I can't seem to get this to work and I am getting the error "'int' object is not iterable"
I would like to create a query set that returns the row of data that matches the query
for example, if the current user's id is 10 I would like to get all the cars hired by the user. All the data stored in the rows where car_hirer_is == 10
This part is causing the error:
...(user_id__in=current_users_id)
When you add the __in part it makes django think that current_users_id is an iterable but current_users_id = request.user.id is a single id. To make it work change it to:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(customer__user_id=current_users_id)
I would also reconsider the names of the variables (current_user_id instead of current_users_id and cars_hired instead of car_hired).

Method to get product price for a given customer

I need to retrieve product price via XMLRPC.
I am using Product Price Lists so each customer can be assigned a given price list which gives specific discounts based on categories, etc.
I am struggling to find which method can be used to retrieve the price for a given product_template id at a given quantity, if that is actually possible.
So far I have not been able to try any specific method as I can not identify how can this be achieved without actually creating a sales order.
The module 'product' holds the pricelist mechanics. The model product.pricelist has a really nice method get_product_price(), which could be easily used server-side but not for the external/web API.
But if you have the possibility to write a little custom module, do that and override the model product.pricelist. Add the possibility to use this method, like:
Origin Method which can't be used because parameters are RecordSets:
def get_product_price(self, product, quantity, partner, date=False, uom_id=False):
""" For a given pricelist, return price for a given product """
self.ensure_one()
return self._compute_price_rule([(product, quantity, partner)], date=date, uom_id=uom_id)[product.id][0]
"Wrapper" for external/web API:
def web_api_get_product_price(
self, product_id, quantity, partner_id, date=False, uom_id=False):
""" For a given pricelist, return price for a given product
callable from web api"""
self.ensure_one()
# get records
product = self.env['product.product'].browse(product_id)
partner = self.env['res.partner'].browse(partner_id)
# call origin method
return self.get_product_price(
product, quantity, partner, date=date, uom_id=uom_id)
Now you can call this method, an example:
import xmlrpclib
db = 'db_name'
password = 'admin'
common = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/2/common')
uid = common.authenticate(db, 'admin', password, {})
models = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/2/object')
pricelist_id = 1
product_id = 5
partner_id = 7
quantity = 20
price = models.execute_kw(
db, uid, password, 'product.pricelist',
'web_api_get_product_price',
[[pricelist_id], product_id, quantity, partner_id], {})

Django complex filter and order

I have 4 model like this
class Site(models.Model):
name = models.CharField(max_length=200)
def get_lowest_price(self, mm_date):
'''This method returns lowest product price on a site at a particular date'''
class Category(models.Model):
name = models.CharField(max_length=200)
site = models.ForeignKey(Site)
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category)
class Price(models.Model):
date = models.DateField()
price = models.IntegerField()
product = models.ForeignKey(Product)
Here every have many category, every category have many product. Now product price can change every day so price model will hold the product price and date.
My problem is I want list of site filter by price range. This price range will depends on the get_lowest_price method and can be sort Min to Max and Max to Min. Already I've used lambda expression to do that but I think it's not appropriate
sorted(Site.objects.all(), key=lambda x: x.get_lowest_price(the_date))
Also I can get all site within a price range by running a loop but this is also not a good idea. Please help my someone to do the query in right manner.
If you still need more clear view of the question please see the first comment from "Ishtiaque Khan", his assumption is 100% right.
*In these models writing frequency is low and reading frequency is high.
1. Using query
If you just wanna query using a specific date. Here is how:
q = Site.objects.filter(category__product__price__date=mm_date) \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
It will return a list of Site with lowest price on mm_date fall within range of min_price - max_price. You can also query for multiple date using query like so:
q = Site.objects.values('name', 'category__product__price__date') \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
2. Eager/pre-calculation, you can use post_save signal. Since the write frequency is low this will not be expensive
Create another Table to hold lowest prices per date. Like this:
class LowestPrice(models.Model):
date = models.DateField()
site = models.ForeignKey(Site)
lowest_price = models.IntegerField(default=0)
Use post_save signal to calculate and update this every time there. Sample code (not tested)
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Price)
def update_price(sender, instance, **kwargs):
cur_price = LowestPrice.objects.filter(site=instance.product.category.site, date=instance.date).first()
if not cur_price:
new_price = LowestPrice()
new_price.site = instance.product.category.site
new_price.date = instance.date
else:
new_price = cur_price
# update price only if needed
if instance.price<new_price.lowest_price:
new_price.lowest_price = instance.price
new_price.save()
Then just query directly from this table when needed:
LowestPrice.objects.filter(date=mm_date, lowest_price__gte=min_price, lowest_price__lte=max_price)
Solution:
from django.db.models import Min
Site.objects.annotate(
price_min=Min('categories__products__prices__price')
).filter(
categories__products__prices__date=the_date,
).distinct().order_by('price_min') # prefix '-' for descending order
For this to work, you need to modify the models by adding a related_name attribute to the ForeignKey fields.
Like this -
class Category(models.Model):
# rest of the fields
site = models.ForeignKey(Site, related_name='categories')
Similary, for Product and Price models, add related_name as products and prices in the ForeignKey fields.
Explanation:
Starting with related_name, it describes the reverse relation from one model to another.
After the reverse relationship is setup, you can use them to inner join the tables.
You can use the reverse relationships to get the price of a product of a category on a site and annotate the min price, filtered by the_date. I have used the annotated value to order by min price of the product, in ascending order. You can use '-' as a prefix character to do in descending order.
Do it with django queryset operations
Price.objects.all().order_by('price') #add [0] for only the first object
or
Price.objects.all().order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('-price') #add [0] for only the first object
I think this ORM query could do the job ...
from django.db.models import Min
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('price_min')
or /and for reversing the order :
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('-price_min')

representing manytomanyfield with extra attributes in django

I have a situation where i need to store a table of "Product"'s and a table of "Order"'s on these products.
One order consists of many ("Product", "Quantity") tuples where quantity is a float expressed in tonnes.
I considered the followng implementation, but i don't think having a table of arbitrary products and quantities would be a very good design decision.
class Product(models.Model):
name = models.CharField("Name", max_length=50)
description = models.TextField("Description", blank=True)
class ProductOrder(models.Model):
unit = "tonnes"
product = models.ManyToManyField('Product')
quantity = models.FloatField('Quantity')
class Order(models.Model):
products = models.ManyToManyField('ProductOrder')
date = models.DateField('Date')
Am I overlooking an obvious solution? How would you implement this relationship to lead to the most DRY code. (PS. I don't want to have separate lists of products and quantities and have to rely implicitly on their ordering.)
In django, you can use through to create such intermediate table and maintain order specific attributes in there.
You can implement it as
class Product(models.Model):
name = models.CharField("Name", max_length=50)
description = models.TextField("Description", blank=True)
class Order(models.Model):
products = models.ManyToManyField('Product', through="OrderDetail")
date = models.DateField('Date')
class OrderDetail(models.Model):
unit = "tonnes"
product = models.ForeignKey('Product')
order = models.ForeignKey('Order')
quantity = models.FloatField('Quantity')
The documentation explains how to use and work with such design.