I am using Odoo and python.
I am trying to search for similar products in "purchase.order.line" quoted by different vendors and then get the cheapest vendor.
Below are the pics:
Vendor 1
Vendor 2
The upper section is class PurchaseOrder
and where there is Products and price is class PurchaseOrderLine
The below code is what I was trying to do for the cheapest product in the second class "purchase.order.line".
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
supplier_prequalification_id = fields.Many2one("supplier.prequalification", string="Supplier Prequalification")
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
supplier_prequalification_id = fields.Many2one("supplier.prequalification", related="order_id.supplier_prequalification_id", string="Supplier Prequalification", store=True)
cheapest = fields.Boolean(string="Cheapest Price", compute="compute_cheapest")
vendor = fields.Many2one("res.partner", string="Vendor", related="partner_id", store=True)
count = fields.Float()
def compute_cheapest(self):
for rec in self:
partner = rec.env['purchase.order'].search([('partner_id', '=', rec.partner_id.id)])
for each_partner in partner:
line = each_partner.order_line
for item in line:
if item.product_id.__________:
item.count = min(item.price_unit)
if item.count:
item.write({'cheapest': True})
else:
item.write({'cheapest': False})
The place where I have put the dash is where I am stuck. How can I search in every vendor's oder_line and find similar products quoted by them. The cheapest price for that product I can store it on count float and then if count I do the last code of selecting boolean true.
Doesn't have to store cheapest on count. We can just say cheapest write boolean.
....
item.write({'cheapest': True})
else:
item.write({'cheapest': False})
NB: I have set field quality to Boolean that's why in the end I am writting "True" or "False"
Or is there a better way to do it
Any help will be appreciated
I'm a newbie in Django and I have some questions about making queries by QuerySet API.
For instance, I have User, his Orders, and its Statuses
class User(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
is_active = models.BooleanField()
class OrderStatus(models.Model):
name = models.CharField(max_length=100)
class Order(models.Model):
number = models.CharField(max_length=10)
amount = models.DecimalField(max_digits=19, decimal_places=2)
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name="orders")
order_status = models.ForeignKey(OrderStatus, on_delete=models.PROTECT)
creation_datetime = models.DateTimeField(auto_now_add=True)
# Some filtering field
filtering_field = models.IntegerField()
I combined all of my questions to this one query:
Get active users with some additional data for each user:
'Amount' of the Orders filtered by 'filtering_field' and aggregated by Min and Max
'Number' and 'Amount' of the first Order filtered by 'filtering_field'
Count of the Orders filtered by 'filtering_field', aggregated by Count and grouped by 'Order Status'. This grouping means that data from query #1 and #2 can be duplicated and it's ok.
I could make this query in T-SQL by 3 separated subquery with own grouping, filtering, ordering:
SELECT
u.id,
u.first_name,
u.last_name,
ts.min_amount,
ts.max_amount,
first_order.number as first_order_number,
first_order.amount as first_order_amount,
cnt.order_status_id,
cnt.cnt
FROM
[User] u
-- 1. 'Amount' of the Orders filtered by 'filtering_field' and aggregated by Min and Max
LEFT OUTER JOIN (
SELECT
[user_id],
MIN(amount) min_amount,
MAX(amount) max_amount
FROM
[Order]
WHERE
filtering_field = 1
GROUP BY
[user_id]
) ts ON u.id = ts.[user_id]
-- 2. 'Number' and 'Amount' of the first Order filtered by 'filtering_field'
OUTER APPLY (
SELECT TOP 1
o.number,
o.amount
FROM
[Order] o
WHERE
u.id = o.[user_id] AND
o.filtering_field = 2
ORDER BY
o.creation_datetime
) first_order
-- 3. Count of the Orders filtered by 'filtering_field', aggregated by Count and grouped by 'Order Status'.
LEFT OUTER JOIN (
SELECT
[user_id],
order_status_id,
COUNT(*) cnt
FROM
[Order]
WHERE
filtering_field = 3
GROUP BY
[user_id],
order_status_id
) cnt ON u.id = cnt.[user_id]
WHERE
u.is_active = 1
How I can do the same by QuerySet API?
Query #1 I can do Min and Max in Annotate.
data = User.objects.filter(
Q(is_active=True)
).values(
'id',
'first_name',
'last_name',
).annotate(
min_amount=Min(
'orders__amount',
filter=Q(orders__filtering_field=1)
),
max_amount=Max(
'orders__amount',
filter=Q(orders__filtering_field=1)
)
)
But what about query #2 & #3?
I've considered Subquery(), but It supports the only one output value.
I mean if you wanna get 5 fields from 1 queryset, sql server runs 5 queries. I think it's not good for performance.
How I can join the first order once to use its fields and How can I use Count() with grouping by filtered rows of child model?
I'd like to use .prefetch_related() as a substitution of Subquery in T-SQL for each query like this:
Prefetch(
'orders',
queryset=Order.objects.filter(filtering_field=1)..., #staff with .values(), annotate(Min(), Max()) and etc.
to_attr='pf_query_1'
)
And then use 'pf_query_1' like 'orders__pf_query_1__amount' in User.objects...values()...annotate().
But I can't use .values() in Prefetch as well as 'pf_query_1' as a model field.
So what is the best practice to make this one query by QuerySet API?
I'd like to see the whole QuerySet API query just like T-SQL query
Have you considered the Django Subquery as described in the docs?
Regarding your 3rd question the only approach coming to my mind is dynamically creating the annotations.
Here a (tested) code sample using your models:
def test_query(self):
# 1st question
min_order = Order.objects.filter(user=OuterRef('pk'), filtering_field=1)\
.order_by().values('user').annotate(min=Min('amount')).values('min')
max_order = Order.objects.filter(user=OuterRef('pk'), filtering_field=1)\
.order_by().values('user').annotate(max=Max('amount')).values('max')
# 2nd question
first_number = Order.objects.filter(user=OuterRef('pk'), filtering_field=1)\
.order_by().values('user').annotate(fnumber=F('number')).values('fnumber')
first_amount = Order.objects.filter(user=OuterRef('pk'), filtering_field=1)\
.order_by().values('user').annotate(fnumber=F('amount')).values('amount')
kwargs = {
'min': Subquery(min_order, output_field=DecimalField()),
'max': Subquery(max_order, output_field=DecimalField()),
'first_n': Subquery(first_number, output_field=CharField()),
'first_a': Subquery(first_amount, output_field=DecimalField())
}
# 3rd question
for o in OrderStatus.objects.all():
kwargs['%s_count' % o.name] = \
Subquery(Order.objects.filter(user=OuterRef('pk'), filtering_field=1, order_status=o)\
.order_by().values('user').annotate(c=Count('pk')).values('c'), output_field=IntegerField())
# Putting it all together
qs2 = User.objects.annotate(**kwargs)
# Testing the results
for user in qs2:
v = Order.objects.filter(user=user, filtering_field=1).aggregate(Min('amount'), Max('amount'))
self.assertEqual(v['amount__min'], user.min)
self.assertEqual(v['amount__max'], user.max)
v = Order.objects.filter(user=user, filtering_field=1).first()
self.assertEqual(v.number, user.first_n)
self.assertEqual(v.amount, user.first_a)
for o in OrderStatus.objects.all():
v = Order.objects.filter(user=user, filtering_field=1, order_status=o).count()
if v == 0:
v = None
k = '%s_count' % o.name
v1 = getattr(user, k)
self.assertEqual(v, v1)
# The sql
print(qs2.query)
Please note:
The code is part of a TestCase where I put it to check if it worked
as expected
I know some parts of the query can be generated without
Subquery using the filter attribute of the aggregation functions. As
this filter attribute was only introduced in Django 2.0 and not
supported in the LTS version 1.11 I did not use it.
EDIT: Here is another approach I came up with starting with a "base queryset" and annotating that one:
def test_query2(self):
qs = Order.objects.filter(filtering_field=1).values('user', 'order_status').distinct()
# 1st question
min_order = Order.objects.filter(user=OuterRef('user'), filtering_field=1)\
.order_by().values('user').annotate(min=Min('amount')).values('min')
max_order = Order.objects.filter(user=OuterRef('user'), filtering_field=1)\
.order_by().values('user').annotate(max=Max('amount')).values('max')
# 2nd question
first_number = Order.objects.filter(user=OuterRef('user'), filtering_field=1)\
.order_by().values('user').annotate(fnumber=F('number')).values('fnumber')
first_amount = Order.objects.filter(user=OuterRef('user'), filtering_field=1)\
.order_by().values('user').annotate(fnumber=F('amount')).values('amount')
# 3rd question
total_count = Order.objects.filter(user=OuterRef('user'), filtering_field=1, order_status=OuterRef('order_status'))\
.order_by().values('user').annotate(c=Count('pk')).values('c')
qs2 = qs.annotate(
min = Subquery(min_order, output_field=DecimalField()),
max = Subquery(max_order, output_field=DecimalField()),
first_n = Subquery(first_number, output_field=CharField()),
first_a = Subquery(first_amount, output_field=CharField()),
c = Subquery(total_count, output_field=IntegerField())
)
# Testing the results
for d in qs2:
v = Order.objects.filter(user=d['user'], filtering_field=1).aggregate(Min('amount'), Max('amount'))
self.assertEqual(v['amount__min'], d['min'])
self.assertEqual(v['amount__max'], d['max'])
v = Order.objects.filter(user=d['user'], filtering_field=1).first()
self.assertEqual(v.number, d['first_n'])
self.assertEqual(v.amount, d['first_a'])
v = Order.objects.filter(user=d['user'], filtering_field=1, order_status=d['order_status']).count()
self.assertEqual(v, d['c'])
print(qs2.query)
I have a simple hierarchical model, 3 turtles high. Let's say Artist, Album, Song. What would be an efficient way to filter the resulting tree in my view?
To get an Artist/Album/Song tree to pass to my template, filtered with an arbitrary condition, I'm currently doing something like:
for current_artist in Artist.objects.filter(album__song__genre='funkadelic mariachi').distinct():
yield current_artist
for current_album in Album.objects.filter(song__genre='funkadelic mariachi').distinct():
yield current_album
for current_song in Song.objects.filter(genre='funkadelic mariachi'):
yield current_song
yield 'End of album'
yield 'End of artist'
But I'm pretty certain there must be a much more efficient way than querying all the way to the leaves in every level, unless distinct() and Django's optimisations provide some magical cache from the other side of the rainbow.
Perhaps creating a whole tree (v.g. with every artist and album, not checking for leaves), and then pruning the leafless branches? Or should I be looking at select_related()?
For extra points, some actual test/benchmark/write-up would be welcome. Danke!
P.S: I know of django-mptt's goodness, but it's overkill for this.
The detailed model is not important, as I'm looking for a general solution, but it could be something like:
class Artist:
name = models.CharField(max_length=200)
class Album:
name = models.CharField(max_length=200)
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
class Song:
name = models.CharField(max_length=200)
album= models.ForeignKey(Album, on_delete=models.CASCADE)
genre = models.CharField(max_length=200)
I ended up with the following:
filters = { "genre": 'funkadelic mariachi' }
artist = None
album = None
result = []
# select_related() fetches our chosen songs, and their albums and artists, in a single query
for song in Song.objects.select_related(
'album__artist').filter(**filters):
if album != song.album and album != None:
result.append('End of Album')
if artist != song.album.artist:
if artist != None:
result.append('End of Artist')
artist = song.album.artist
result.append(artist)
if album != song.album:
album = song.album
result.append(album)
result.append(song)
if result:
result.append('End of Album')
result.append('End of Artist')
Not so pretty, but much more efficient. Perhaps prefetch_related() would allow to keep the three loops, using Prefetch('artist', to_attr='filtered_artists') or so, but with one extra query per turtle.
Here is my model
class Wallet(models.Model):
"""
Keep track of the monetary values of a company's wallet
"""
serializer_class = WalletSocketSerializer
company = models.OneToOneField(Company, verbose_name=_('company'))
packaged_credits = models.BigIntegerField(_('packaged credits'), default=0)
purchased_credits = models.BigIntegerField(_('purchased credits'), default=0)
low_credits_threshold = models.BigIntegerField(default=0)
Now i would like to send an alert if the total credits are less than the threshold, this would be the equivalent of getting all low wallets in this SQL
select * from wallets_wallet where (packaged_credits + purchased_credits) < low_credits_threshold;
I want to know how to execute that in django, right now i have tried the following, it works, but i think it should be done in a more Django way:
low_wallets = []
for wallet in Wallet.objects.all():
if wallet.packaged_credits + wallet.purchased_credits < wallet.low_credits_threshold:
low_wallets.append(wallet)
from django.db.models import F
low_wallets = Wallet.objects.filter(
low_credits_threshold__gt=F('packaged_credits')+F('purchased_credits')
)
Wallet.objects.extra(where=["packaged_credits + purchased_credits < low_credits_threshold"])
I need to calculate 90 day after last operation in user's cart!
I have model model ORDERING
class Order(DirtyFieldsMixin, models.Model):
...
items_add_date = models.DateTimeField(null=True, blank=True)
...
my task is. Track the date when user added last item in his own cart and then, if past 90 days and user not added any more items in cart sent to him email.
I'm doing this
def cart_add(request):
...
order.items_add_date = datetime.datetime.now()
order.save()
...
But what and how should I act after?
Not sure what you are struggling with but if you need to determine if its been 90 days then you would add a check:
#query for order
order = Order.get(xxx)
ninety = datetime.datetime.now() - timedelta(days=-90)
if order.items_add_date < ninety:
#send email
If you need to query for things older than 90 days:
order = Order.filter(items_add_date__lt=ninety)
I solved it so
srok = datetime.datetime.now()-timedelta(minutes=1)
user_unbuying = Order.objects.filter(items_add_date__lt=srok)
usr=[]
for unbus in user_unbuying:
if unbus.customer.id not in usr:
if unbus.customer is not None:
if unbus.items_add_date is not None:
usr.append(unbus.customer.id)
#send mail