Can I get this result of PostgreSQL query using Django ORM? - django

I am having trouble with django ORM.
I want to get the data which I can get using PostgreSQL and raw sql query in views. But is there any solution to achieve this using Django ORM.
Here is my models
class Invoice(models.Model):
class PaymentMode(models.TextChoices):
Cash = 0, _('CASH')
Credit = 1, _('CREDIT')
class PaymentStatus(models.TextChoices):
Paid = 0, _('PAID')
Pending = 1, _('PENDING')
total_amount = models.IntegerField()
payment_mode = models.CharField(choices=PaymentMode.choices,
max_length=20,
default=PaymentMode.Credit)
payment_status = models.CharField(choices=PaymentStatus.choices,
default=PaymentStatus.Pending,
max_length=15)
print_date = models.DateField(default=now)
invoice_date = models.DateField(default=now)
created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING)
customer = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
class Payment(models.Model):
class PaymentMethod(models.TextChoices):
Cash = 0, _('CASH')
Cheque = 1, _('CHEQUE')
OnlineTransfer = 2, _('WEB_TRANSFER')
Other = 3, _('OTHER')
invoice = models.ForeignKey(Invoice,
on_delete=models.DO_NOTHING,
default=0)
amount = models.IntegerField()
date = models.DateField(default=now)
recieved_by = models.CharField(max_length=150)
payment_method = models.CharField(choices=PaymentMethod.choices,
default=PaymentMethod.Cash,
max_length=20)
and here is my PostgreSQL query
SELECT
A.id ,
A.total_amount ,
A.payment_mode,
A.payment_status,
A.print_date,
A.invoice_date,
A.created_by_id,
A.customer_id,
coalesce(SUM(P.amount), 0) AS "paid",
(A.total_amount - coalesce(SUM(P.amount), 0)) As "remaining"
FROM
public."Invoicing_invoice" as A
LEFT JOIN public."Invoicing_payment" as P
ON P.invoice_id = A.id
GROUP BY A.id

You can annotate with:
from django.db.models import F, Sum, Value
from django.db.models.functions import Coalesce
Invoice.objects.annotate(
paid=Coalesce(Sum(payment__amount), Value(0)),
remaining=F('total_amount') - Coalesce(Sum(payment__amount), Value(0))
)
The Invoices that arise from this queryset will have two extra attribute .paid and .remaining with the paid and remaining amount of the invoice.

Related

how can i use django filter with multiple value select

I want to filter my data based on city , how can i filter my data if the user choose more than one city using django filter
class games(generics.ListAPIView):
queryset = Game.objects.filter(start_date__gte=datetime.today())
serializer_class=GameSerializers
filter_backends = [DjangoFilterBackend,filters.OrderingFilter]
filterset_fields = ['id','city','level']
game model
class Game(models.Model):
city = models.CharField(max_length=255)
gender = models.ForeignKey(Gender,on_delete=models.CASCADE)
level = models.ForeignKey(Level,on_delete=models.CASCADE)
host = models.ForeignKey(Host,on_delete=models.CASCADE)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
fees = models.IntegerField()
indoor = models.BooleanField()
capacity = models.IntegerField()
age_from = models.IntegerField()
age_to = models.IntegerField()
description = models.CharField(max_length=255)
earned_points = models.IntegerField()
created_at = models.DateTimeField(default=django.utils.timezone.now)
image = models.ImageField(upload_to="GameImage",null=True)
history = HistoricalRecords()
Game.objects.filter(city__in=['Paris', 'London'])
Something like that ?
I'm not sure if this gonna work but try this:
filterset_fields = ['id','city__in','level']

How to annotate queryset with another queryset

Now I'm trying to build complex queryset that uses annotations with conditional related queries.
I have the following models:
class MenuItemCategory(CreateUpdateModel):
name = models.CharField(max_length=255, blank=True, null=True)
class MenuItem(CreateUpdateModel):
category = models.ForeignKey(MenuItemCategory, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
class LineItem(models.Model):
order = models.ForeignKey(Orders, blank=True, null=True)
menu_item = models.ForeignKey(MenuItems, blank=True, null=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.DecimalField(max_digits=10, decimal_places=3)
amount = models.DecimalField(max_digits=10, decimal_places=2)
class Order(CreateUpdateModel):
waiter = models.ForeignKey(Employees, blank=True, null=True)
guests_count = models.IntegerField(blank=True, null=True, default=0)
closed_at = models.DateTimeField(blank=True, null=True, db_index=True)
class Employees(CreateUpdateModel):
restaurant = models.ForeignKey(Restaurants, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
My goal is to build json with following scheme:
[
{
employee_name: 'Jane',
menu_item_categories: [
{
name: 'Drinks',
line_items_quantity: 10, //times when this waiter brings any item from this category to the customer at the period
amount: 49.00, // price of all drinks sold by this waiter at the period
menu_items: [
name: 'Vodka',
amount: 1.00,
line_items_quantity: 4, # times when this item has been ordered for this waiter at the period
]
}
],
visits: 618,
guests: 813,
cycle_time: 363
}
]
With following serializer:
class EmployeeSerializer(serializers.ModelSerializer):
name = serializers.CharField(max_length=255)
visits = serializers.SerializerMethodField()
guests = serializers.SerializerMethodField()
cycle_time = serializers.SerializerMethodField()
menu_item_categories = serializers.SerializerMethodField()
def get_visits(self, obj):
# works
def get_guests(self, obj):
# works
def get_cycle_time(self, obj):
# works
def get_menu_item_categories(self, obj):
qs = MenuItemCategories.objects.annotate(
line_items_quantity=Count('menuitems__lineitems__order',
filter=Q(
menuitems__lineitems__order__closed_at__range=self.context.get('period'),
menuitems__lineitems__order__waiter=obj)
),
amount=Sum('menuitems__lineitems__amount',
filter=Q(
menuitems__lineitems__order__closed_at__range=self.context.get('period'),
menuitems__lineitems__order__waiter=obj)
),
menu_items=Subquery(
MenuItems.objects.filter(
lineitems__order__closed_at__range=self.context.get('period'),
lineitems__order__waiter=obj
).annotate(amount=Sum('lineitems__amount', filter=Q(lineitems__order__closed_at__range=self.context.get('period'),
lineitems__order__waiter=obj)))
)
)
return MenuItemCategorySerializer(qs, many=True).data
But when I try to build menu_item_categories value - it gives me an error: subquery must return only one column. As I understand, my goal is to annotate categories queryset with custom subquery and my trouble is that I don't understand how subquery works or I use incorrect toolkit to build orm query. So, how can I build this json with orm query and this serializer?
UPD
current query is
SELECT
"menu_item_categories"."id", "menu_item_categories"."created_at",
"menu_item_categories"."updated_at", "menu_item_categories"."restaurant_id",
"menu_item_categories"."name", "menu_item_categories"."is_active",
COUNT("line_items"."order_id") AS "line_items_quantity",
(SELECT
U0."id", U0."created_at", U0."updated_at",
U0."restaurant_id", U0."category_id", U0."name",
SUM(U1."amount") AS "amount"
FROM "menu_items"
U0 INNER JOIN "line_items" U1
ON (U0."id" = U1."menu_item_id")
INNER JOIN "orders" U2
ON (U1."order_id" = U2."id")
WHERE (
U2."waiter_id" = 5 AND U2."closed_at"
BETWEEN 2017-12-20 14:19:16+00:00 AND 2017-12-26 14:19:16+00:00)
GROUP BY U0."id")
AS "menu_items",
SUM("line_items"."amount") AS "amount"
FROM "menu_item_categories"
LEFT OUTER JOIN "menu_items"
ON ("menu_item_categories"."id" = "menu_items"."category_id")
LEFT OUTER JOIN "line_items"
ON ("menu_items"."id" = "line_items"."menu_item_id")
GROUP BY "menu_item_categories"."id",
(
SELECT
U0."id", U0."created_at",
U0."updated_at", U0."restaurant_id",
U0."category_id", U0."name", SUM(U1."amount"
) AS "amount"
FROM "menu_items" U0
INNER JOIN "line_items" U1
ON (U0."id" = U1."menu_item_id")
INNER JOIN "orders" U2
ON (U1."order_id" = U2."id")
WHERE (U2."waiter_id" = 5
AND U2."closed_at"
BETWEEN 2017-12-20 14:19:16+00:00
AND 2017-12-26 14:19:16+00:00)
GROUP BY U0."id")
You can't get that kind of structure directly with one single query, simply because of how RDBMS's work. You can, however, get a big result with lots of redundant information, all the way from the simplest items so you can group data programatically to generate your json structure, or you can just do that in one step by iterating over your querysets:
t_range=self.context.get('period')
employees = Employees.objects.filter(order__closed_at__range=t_range) \
.annotate(
visits=Count(...),
guests=Count(...),
cycle_time=Sum(...),
)
result = []
for employee in employees:
menu_item_categories = MenuItemCategory.objects.filter(menuitem__lineitem__order__waiter=employee) \
.annotate(
line_items_quantity=Count(...),
amount=Sum(...),
)
_cats = []
for cat in menu_item_categories:
menu_items = cat.menuitem_set.filter(order__waiter=employee) \
.annotate(
amount=Sum(...),
line_items_quantity=Count(...),
)
_menu_items = []
for menu_item in menu_items:
_menu_item = {
'name': menu_item.name,
'amount': menu_item.amount,
'line_items_quantity': menu_item.line_items_quantity,
}
_menu_items.append(_menu_item)
_cats.append({
'name': cat.name,
'line_items_quantity': cat.line_items_quantity,
'amount': cat.amount,
'menu_items': _menu_items
})
result.append({
'employee_name': employee.name,
'visits': employee.visits,
'guests': employee.guests,
'cycle_time': employee.cycle_time,
'menu_item_categories': _cats
})
Sure, this will hit the database more than once so unless you prefer performance over this approach, this will do the trick.

Django add aggregate operation to a query result

I'm trying to do an aggregate operation between two tables using Django, my models are:
class Cusinetype(models.Model):
hometype_en = models.TextField()
active = models.BooleanField()
hometype_es = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = 'cusinetype'
class Foodpreferences(models.Model):
id_client = models.ForeignKey(Client, models.DO_NOTHING, db_column='id_client')
id_cusinetype = models.ForeignKey(Cusinetype, models.DO_NOTHING, db_column='id_cusinetype')
created_at = models.DateTimeField()
class Meta:
managed = False
db_table = 'foodpreferences'
The query that I'm trying to build is:
SELECT
ct.id,
ct.hometype_en,
ct.hometype_es
,
((SELECT COUNT(*)
FROM foodpreferences fp
WHERE fp.id_cusinetype = ct.id AND fp.id_client = 3 ) > 0 ) selected
FROM
Cusinetype ct
I'm trying to generate a model, to store the information of those tables in a single one query, but anything works.
Someone has an idea about how to do it?
serializers.py
class PreferencesSerializer(serializers.ModelSerializer):
selected = serializers.IntegerField()
class Meta:
model = Cusinetype
fields = ('id', 'trucktype_en', 'trucktype_es', 'selected')
views.py
qs = Cusinetype.objects.filter().filter(active = True)
qs = qs.annotate(
selected=Sum(Case(
When(foodpreferences__id_client=3, then=1),
output_field=IntegerField()
))
)
serializers = PreferencesSerializer(qs, many = True)
return Response({ "result": serializers.data })

Django: Query involving Foreign Key

I have models.py
class employees(models.Model):
emp_id=models.PositiveIntegerField()
emp_name = models.CharField(max_length = 100)
emp_lname = models.CharField(max_length = 100)
emp_loc=models.CharField(max_length=5,choices=LOCATION)
manager_id=models.ForeignKey('self',null=True,blank=True)
class leave(models.Model):
employee = models.ForeignKey(employees, on_delete=models.CASCADE, default='1')
start_date = models.DateField()
end_date = models.DateField()
status=models.CharField(max_length=1,choices=LEAVE_STATUS,default='P')
ltype=models.CharField(max_length=2,choices=LEAVE_TYPE)
class notify(models.Model):
sender_id=models.ForeignKey(leave, related_name='%(class)s_sendername')
receiver_id=models.ForeignKey(leave,related_name='%(class)s_receivername')
date_time=models.DateTimeField(auto_now_add=True)
viewed=models.CharField(max_length=2)
I want the employee id of receiver_id as receiver_id is a foreign key...
When I query
notify.objects.filter(receiver_id__employee__emp_id=1)
I am getting empty queryset but I want the tuples with emp_id=1.
here notify.objects.filter(receiver_id__employee__emp_id=1) Make sure you have assigned emp_id field as it's not auto incremented field.
also try
notify.objects.filter(receiver_id__employee__id=1)

Error computing aggregate over related model in Django

I have the following models:
class Contest(models.Model):
id_contest = models.AutoField(primary_key=True)
name = models.CharField(max_length=50, blank=False)
class Registration(models.Model):
id_registration = models.AutoField(primary_key=True)
team = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING,
related_name='registrations',
related_query_name='registration')
contest = models.ForeignKey(
Contest,
on_delete=models.DO_NOTHING,
related_name='registrations',
related_query_name='registration')
created_at = models.DateTimeField(null=True)
confirmed_at = models.DateTimeField(null=True)
class Submission(models.Model):
id_submission = models.AutoField(primary_key=True)
team = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING,
related_name='submissions',
related_query_name='submission')
contest = models.ForeignKey(
Contest,
on_delete=models.DO_NOTHING,
related_name='submissions',
related_query_name='submission')
submitted_at = models.DateTimeField(null=True)
is_valid = models.NullBooleanField()
public_score = models.FloatField(null=True)
And I'm working on a leaderboard query (PostgreSQL) like:
select
r.team_id,
max(t.username) as team_name,
count(s.team_id) as num_submissions,
min(s.public_score) as score,
max(s.submitted_at) as last_submission,
max(r.confirmed_at) as confirmation
from api_registration r
left join auth_user t on (r.team_id = t.id)
left join api_submission s on (r.contest_id = s.contest_id and s.team_id = t.id and s.is_valid = TRUE)
where r.contest_id = 1
group by r.team_id
order by score ASC, last_submission DESC;
Which returns the results I want.
However, when translating into Django QuerySet operations, the closest I've
come is:
leaderboard = Registration.objects \
.filter(contest=contest, contest__submission__is_valid=True) \
.annotate(team_name=Max('team__username'),
num_submissions=Count('team__submission'),
score=Min('contest__submission__public_score'),
last_submission=Max('contest__submission__submitted_at'),
confirmation=Max('confirmed_at')) \
.order_by('score', '-last_submission')
which generates the query:
SELECT
"api_registration"."id_registration",
"api_registration"."team_id",
"api_registration"."contest_id",
"api_registration"."created_at",
"api_registration"."confirmed_at",
MAX("api_registration"."confirmed_at") AS "confirmation",
MAX("api_submission"."submitted_at") AS "last_submission",
MAX("auth_user"."username") AS "team_name",
MAX("api_submission"."public_score") AS "score",
COUNT(T5."id_submission") AS "num_submissions"
FROM "api_registration" INNER JOIN "api_contest" ON ("api_registration"."contest_id" = "api_contest"."id_contest")
INNER JOIN "api_submission" ON ("api_contest"."id_contest" = "api_submission"."contest_id")
INNER JOIN "auth_user" ON ("api_registration"."team_id" = "auth_user"."id")
LEFT OUTER JOIN "api_submission" T5 ON ("auth_user"."id" = T5."team_id")
WHERE ("api_registration"."contest_id" = 1
AND "api_submission"."is_valid" = True)
GROUP BY "api_registration"."id_registration"
ORDER BY "score" ASC, "last_submission" DESC;
And doesn't properly compute the correct number of submissions per team.
Any help on how to define the Django QuerySet operations to obtain correct results?
Try change this:num_submissions=Count('team__submission'), for num_submissions=Count('team_id'),.
This is to group according to the team_id of the table registration.