Check if data exists in Django annotate queryset - django

Environments
Django 2.0
DRF 3.11
mariaDB
I want to output'N' if at least one of the columns is 0, and'Y' if all of them are 1.
Database
Model
class UserPoint:
user = models.ForeignKey('User', others options)
poinit = models.IntegerField()
used = models.SmallIntegerField(default=0)
deleted = models.SmallIntegerField(default=0)
Expected results
total_point_used : Sum of points where used is 1
used_status : 'N' if any of used has 0 or 'Y' if all values ​​of used are 1
user
username
total_point_used
used_status
1
'AA'
600
'N'
2
'BB'
0
'N'
3
'CC'
500
'Y'
Tried queryset
q = UserPoint.objects.filter(
deleted=0
).values(
username=F('user__username')
.... others values ....
).annotate(
total_point_used=Sum( # Sum of used point
Case(
When(
used=1,
then='point',
), output_field=IntegerField(),
)
),
# I don't know what to do with used_status.
# tried <- not work
#used_status=Case(
# When(
# used=1,
# then=Value('Y')
# ),
# default=Value('N'),
# output_field=CharField()
# )
# I want something like this.
used_status=Exists(used=0, then='N', default='Y')
)
How can I get the value I expect in annotate?

Probably you can use Sum with filter:
from django.db.models import Q, Case, When, Sum
# rest of the code
.annotate(
total_point_used=Sum('point', filter=Q(used=1)),
used_status=Case(
When(
used=1,
then=Value('Y')
),
default=Value('N'),
output_field=CharField()
)
)

Related

How to Django queryset annotate True when all BooleanField of related objects are True else False?

I have a model who looks like this :
class Test(models.Model):
user = models.ForeignKey('users.CustomUser', models.CASCADE)
name = models.CharField(max_length=64)
class TestVersion(models.Model):
test = models.ForeignKey('Test', models.CASCADE)
name = models.CharField(max_length=255)
validation_1 = models.BooleanField(default=False, editable=False)
validation_2 = models.BooleanField(default=False, editable=False)
validation_3 = models.BooleanField(default=False, editable=False)
validation_4 = models.BooleanField(default=False, editable=False)
Sometimes i have like hundreds of TestVersion linked to a Test.
And I want something like :
user_test = Test.objects.filter(
user=request.user
).annotate(
number_of_test=Count('testversion', distinct=True),
all_validation_1="True or False ?", # if all testversion_set.all() of the current test are True, return True else False.
all_validation_2="True or False ?", # same
all_validation_3="True or False ?", # same
all_validation_4="True or False ?", # same
).distinct()
# I Want for example :
test_1 = user_test.first()
test_1_validation_1 = test_1.testversion_set.all().count()
test_1_validation_1_true = test_1.testversion_set.filter(validation_1=True).count()
all_validation_1 = test_1_validation_1 == test_1_validation_true
test_1.all_validation_1 == all_validation_1 # True
# Or something like :
test_1 = user_test.first()
all_validation_1 = all(test_1.testversion_set.all().values_list('validation_1', flat=True))
test_1.all_validation_1 == all_validation_1 # True
I have not been able to find what techniques were used to achieve this level of accuracy with related objects in annotate method.
Any ideas ?
Thank's
Update : Thank's you Sumithran for your answer.
But I don't want all_validated I want to manage all_validation_1 next to all_validation_2 for some check.
If I take example on your solution, it almost work with a little throwback that I don't understant :
test = Test.objects.annotate(
number_of_test=Count("testversion", distinct=True)
).annotate(
all_validation_1=Case(
When(Q(testversion__validation_1=True), then=Value(True)),
default=Value(False),
output_field=BooleanField(),
),
all_validation_2=Case(
When(Q(testversion__validation_2=True), then=Value(True)),
default=Value(False),
output_field=BooleanField(),
)
)
But for some Test objects there is some duplication :
test.filter(name='test_27')
>> <QuerySet [<Test: test_27>, <Test: test_27>]>
test.filter(name='test_27')[0] == test.filter(name='test_27')[1]
>> True
test.filter(name='test_27')[0].all_validation_1
>> True
test.filter(name='test_27')[1].all_validation_1
>> False
What I'm doing wrong ?
You can make use of Django's Conditional expressions in combination with the Q objects.
Give this a try
from django.db.models import Case, When, Value, BooleanField, Count, Q
test = Test.objects.annotate(
number_of_test=Count("testversion", distinct=True)
).annotate(
all_validated=Case(
When(
Q(testversion__validation_1=True)
& Q(testversion__validation_2=True)
& Q(testversion__validation_3=True)
& Q(testversion__validation_4=True),
then=Value(True),
),
default=Value(False),
output_field=BooleanField(),
)
).distinct()
if all of your test validations are True then the value of output filed all_validated will be True otherwise it be False
I finally find an other answer :
from test.models import Test, TestVersion
from django.db.models import Count, Case, When, Exists, OuterRef, Value, BooleanField
test = Test.objects.filter(
user=request.user
).annotate(
number_of_test=Count("testversion", distinct=True),
all_validation_1=Case(
When(
Exists(TestVersion.objects.filter(test=OuterRef('pk'), validation_1=False)),
then=Value(False)
),
default=Value(True),
output_field=BooleanField()
)
).distinct()
In this case :
>> test.first().all_validation_1 == all(test.first().testversion_set.all().values_list('validation_1', flat=True))
True
So I just need to reiterate the same thing for validation_2, 3 and 4 and it should be ok.
But I think it will be a little bit fat code. Or it can be a good practice ?
Let me know before I check the answer.

Count how many times value appears in table

Within my app, I have some forms that allow the user to select from a dropdown and I'm trying to count how many times RED, GREEN and AMBER have been selected across 20 different fields.
I was looking at
from django.db.models import Count
queryset = MyModel.objects.all().annotate(count = Count('my_charfield'))
But I'm not sure how to count the values rather than the field type?
Thanks
You can use conditional aggregation. Something to this effect:
MyModel.objects.aggregate(
red=Sum(
Case(When(my_charfield="RED", then=1),
output_field=IntegerField())
),
green=Sum(
Case(When(my_charfield="GREEN", then=1),
output_field=IntegerField())
),
amber=Sum(
Case(When(account_type="AMBER", then=1),
output_field=IntegerField())
)
)
which will then return a dictionary of values
{red: 1, green: 10, amber: 101}

How do I annotate multiple reverse fields with filters and all?

I have "Product", "OrderItem" and "Stock". I would like the fastest query to determine the stock on hand and the total order quantity for each "Product".
I assumed it would be done like this:
qs = (
Product.objects.all()
.prefetch_related(
Prefetch(
"stock",
queryset=Stock.objects.filter(timestamp__lte=timezone.now()),
),
Prefetch("ordered_items", queryset=OrderItem.objects.all(),),
)
.annotate(
total_on_hand=Sum("stock__quantity"),
total_allocated=Sum("ordered_items__quantity_ordered"),
)
.order_by("id")
)
The above returns results, but the SQL doesn't include my "timestamp__lte" filter shown above. This invalidates the data.

django: sort rows based on number of column matching

I have a model:-
class Userprofile(models.Model):
user=models.OneToOneField(settings.AUTH_USER_MODEL)
education=models.models.CharField(max_length=20,blank=True,null=True)
country=models.CharField(max_length=20,blank=True,null=True)
occupation=models.CharField(max_length=20,blank=True,null=True) ....
for a user profile (let's say: ('masters','India','student') I want to filter all the user profiles ordered by the number of fields it matches with the given user profile i.e first all 3 fields matching profiles then any 2 fields matching profiles and so on.Can anyone suggest a way to do this efficiently?
You can achieve this using conditional expressions.
from django.db.models import Value, Case, When, IntegerField, F
education, country, occupation = 'masters','India','student'
Userprofile.objects.annotate(education_matched=Case(
When(education=education, then=Value(1)),
default=Value(0),
output_field=IntegerField()
), country_matched=Case(
When(country=country, then=Value(1)),
default=Value(0),
output_field=IntegerField()
), occupation_matched=Case(
When(occupation=occupation, then=Value(1)),
default=Value(0),
output_field=IntegerField()
)).
annotate(matched=F('education_matched') + F('country_matched') + F('occupation_matched')).
order_by('matched')

Django selecting a count

I'm fairly new to Django and I want to get the total clients a user has (my users will be selling something through my website) so I have a table called orders where I keep the user_id of the user who purchased and the product_id being purchased. Each product id is related to a product that has a user (the one I'm doing the query for):
select COUNT(distinct(o.user_id)) as total_clients from `order` o
inner join product p on p.id=o.product_id where p.user_id=32;
User id 32 is logged in and I want to show him how many clients purchased his products.
I want to do this in a get instead of filter as it makes more sense.
Here's what my logic tells me to write:
clients = Order.objects.get(
status = Order.COMPLETED,
product__user = self.user
).annotate(
total_clients = Count( 'user', distinct = True )
)
return clients.total_clients
.and here's what it returns:
Exception Type: MultipleObjectsReturned
Exception Value: get() returned more than one Order -- it returned 2!
I could probably be running a query instead of using the orm but I don't want that. This is a rather simple query that I'm sure Django is handling very easily and I want to keep away from writing strings in my code.
Here's my model setup:
class UserProfile( models.Model ):
user = models.OneToOneField( User, related_name = 'profile' )
....
def get_total_clients( self ):
clients = Order.objects.get(
status = Order.COMPLETED,
product__user = self.user
).annotate(
total_clients = Count( 'user', distinct = True )
)
return clients.total_clients
class Order( models.Model ):
PENDING = 0
COMPLETED = 1
REFUNDED = 2
STATUS_CHOICES = (
(PENDING, "Pending"),
(COMPLETED, "Completed"),
(REFUNDED, "Refunded"),
)
user = models.ForeignKey( User, related_name = "orders" )
product = models.ForeignKey( Product, related_name = "orders" )
amount = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0.00 )
status = models.SmallIntegerField(
choices = STATUS_CHOICES, default = PENDING, db_index = True
)
class Product( models.Model ):
user = models.ForeignKey( User, related_name = "unlocks" )
name = models.CharField( max_length = 255 )
Django queryset have a count method:
clients = Order.objects.filter(
status = Order.COMPLETED,
product__user = self.user
)
return clients.count()
If i got you right, you are intrested in the amount of consumers who ordered a product from a user. Some docs, that may be helpful.
My suggestion is:
result = Product.objects.distinct().filter(
# or any query:
orders__status=Order.COMPLETED,
user=default_user
).aggregate( # see the difference between aggregate() and annotate()
total_users=Count('orders__user', distinct=True)
)
I expect the result to be: {'total_users': NUM_OF_USERS}
In raw SQL it will be something like:
SELECT DISTINCT COUNT(DISTINCT "main_order"."user_id") AS
"total_users" FROM "main_product" INNER JOIN "main_order" ON (
"main_product"."id" = "main_order"."product_id" ) WHERE
("main_order"."status" = STATUS AND "main_product"."user_id" = USER_ID)
Is that what you wanted?