How to count quantity with distinct? - django

I have 2 templates, one representing a product sheet and the other one an actual product in stock.
The stock can have several products that have the same product sheet.
Example:
I can have a product record "Water bottle", and several "water bottle" in the stock.
My models:
class Stock(models.Model):
machine = models.ForeignKey(
"machine.Machine",
verbose_name=_("machine"),
related_name="machine_stock",
on_delete=models.CASCADE
)
product = models.ForeignKey(
"client.Product",
verbose_name=_("product"),
related_name="product_stock",
on_delete=models.CASCADE
)
epc = models.CharField(_("EPC"), max_length=80)
dlc = models.DateField(_("DLC"))
class Product(models.Model):
name = models.CharField(_('Name'), max_length=255)
[...]
I want to retrieve the products in stock sorted by both DLC and name.
On my frontend I want to display a table with for each row:
the name of the product
the dlc
the number of products with this name and this dlc
Example:
If I have 2 product sheets (Product Model) :
water bottle
bottle of coca cola
and I have 5 products in stock (Stock Model) :
2 bottles of water whose dlc is 02/04/2022
2 bottles of cola whose dlc is the 02/04/2022
1 bottle of cola whose dlc is 03/04/2022
I want to display 3 lines in my table:
Quantity | Name | DLC
2 | water | 02/04/2022
2 | cola | 02/04/2022
1 | cola | 03/04/2022
I tried with
queryset = (
Stock.objects.all()
.select_related('product')
.select_related('machine')
.annotate(quantity=Count("product__name", distinct=True))
.distinct("dlc", "product__name",)
.order_by("-dlc")
)
But django ORM don't accept annotate and distinct in the same query.

It's works with:
queryset = (
Stock.objects.all()
.select_related('product')
.select_related('machine')
#.annotate(quantity=Count("product__name", distinct=True))
.distinct("dlc", "product__name",)
.order_by("-dlc")
)
for result in queryset:
result.quantity = Stock.objects.filter(dlc=result.dlc, product__name=result.product.name).count()
But I'm not sure that is the best method to use.

Related

How to get count of subjects taken by each student?

I've two models 'Students' and 'Enrollments'.
The schema for these is as below:
class Students(models.Model):
id = models.AutoField(primary_key=True, unique=True)
name = models.CharField()
class Enrollments(models.Model):
enroll_id = models.AutoField(primary_key=True, unique=True)
student_id = models.ForeignKey(Students, on_delete=models.CASCADE)
subjects = models.charField()
I'm trying to achieve the result of following SQL query in Django Rest Framework, for getting number of subjects enrolled by students (individually).
select
s.id, s.name, count(e.subjects) as count
from Students as s
left outer join Enrollments as e
on e.student_id_id = s.id
group by s.id, s.name, e.subjects
order by count asc;
This query returns result like:
---------------------------
| id | name | count |
---------------------------
| 1 | a | 1 |
| 2 | b | 0 |
| 3 | c | 2 |
---------------------------
Can anyone please help me acheive this kind of result.
Note: I need 0 count students details also.
What you can do is when you are creating a serializer, you can add a serializer method field which will get the count for you.
Add this at the top of your serializer:
count = serializers.SerializerMethodField('get_count')
Then add a function inside your serializer like this:
def get_count(self, obj):
try:
return Enrollments.objects.filter(student_id=obj.id).count()
except:
return None
Finally, add 'count' to your field list. You can then add as many fields as you want. I hope this will get you your desired result. Also don't forget to use "select_related" in the ORM inside your view to reduce the amount of queries.

Django how to filter a foreign key object only with specific value

I have two tables.
Table Product
id | name |
Table Discount
id | product_id | is_deleted
Two models are:
class Product(models.Model):
name = models.CharField(max_length=10)
class Discount(models.Modle):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
is_deleted = models.BooleanField(default=True)
product_id is the foreign key of product table's id, and is_deleted is a boolean field.
How can I filter all products only with is_deleted discount? Notice those two tables may be large, so .exclude() is not fit for this case.
product_ids_subquery = Discount.objects.values('product').annotate(
deleted=(Count('product') - Count('product', filter=Q(is_deleted=True)))
).filter(deleted=0).values('product')
products_qs = Product.objects.filter(id__in=product_ids_subquery)
we can identify that products only with is_deleted discount with the help of count after group by product. For each product,
calculate how many records are present in discount table. let say total_count = Count('product')
calculate how many records are present which is deleted. let say deleted_count = Count('product', filter=Q(is_deleted=True))
deleted = (total_count - deleted_count) gives you a no. which is not deleted
if deleted == 0 then you can say all mapped records are deleted
products = Product.objects.filter(discount__is_deleted=True)

Gel all the values of Many2Many intermediate table with their respective name

I have the following database structure:
Products
--------
id name
1 prodA
2 prodB
Products_Invoices
-----------------
product_id Invoice_id
1 1
2 1
Invoices
--------
id name
1 InvA
2 InvB
Which is most correct way to retrieve all the values of the Products_Invoices table but bring the names of their respective row in parent tables.
Since you stated that you have
products = models.ManyToManyField('Product', blank=True)
You can do the following:
invoices = Invoice.objects.all()
for invoice in invoices:
for product in invoice.products.all():
print(product.name)
For efficiency, you can also prefetch all the products for the invoice using prefetch_related
invoices = Invoice.objects.all().prefetch_related('products')

LEFT OUTER JOIN in subquery

Consider a simple User and Content models setup. I would like to get the distribution of content per user, including 0 for users without content:
per_user | count
----------+-------
0 | 89
1 | 15
2 | 14
For the sake of this question the barebone models are:
class User(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class Content(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
One way to do this in pure SQL is:
SELECT
per_user,
count(per_user) count
FROM (
SELECT COUNT(c.id) per_user
FROM app_user u
LEFT JOIN app_content c ON (c.user_id = u.id)
GROUP BY u.id
) AS sub
GROUP BY
per_user
ORDER BY
per_user DESC;
I can do this to get the per_user count:
User.objects.annotate(per_user=Count("content")).values("per_user")
Unfortunately I cannot stick another .annotate(c=Count("per_user")) at the end of this:
FieldError: Cannot compute Count('per_user'): 'per_user' is an aggregate

django accessing raw many to many created table fields

Model:
class Subjects (models.Model):
name = models.CharField(max_length=100)
places = models.CharField(max_length=100)
class Student (models.Model):
name = models.CharField(max_length=40)
lastname = models.CharField(max_length=80)
subjects = models.ManyToManyField(Subjects, blank=True)
Django creates appname_student_subjects when I use model above.
appname_student_subjects table looks for example, like this:
id | student_id | subjects_id
-----------------------------------------
1 | 1 | 10
2 | 4 | 11
3 | 4 | 19
4 | 5 | 10
...
~1000
How can I access subjects_id field and count how many times subjects_id exists in the table above (and then do something with it). For example: If subject with id 10 exists two times the template displays 2. I know that I should use "len" with result but i don't know how to access subject_id field.
With foreign keys I'm doing it like this in a for loop:
results_all = Students.objects.filter(subject_id='10')
result = len(results_all)
and I pass result to the template and display it within a for loop but it's not a foreign key so it's not working.
You can access the through table directly.
num = (Students.subjects # M2M Manager
.through # subjects_students through table
.objects # through table manager
.filter(student_id=10) # your query against through table
.count())