LEFT JOIN with other param in ON Django ORM - python

I have the following models:
class Customer(models.Model):
name = models.CharField(max_length=255)
email = models.EmailField(max_length = 255, default='example#example.com')
authorized_credit = models.IntegerField(default=0)
balance = models.IntegerField(default=0)
class Transaction(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
payment_amount = models.IntegerField(default=0) #can be 0 or have value
exit_amount = models.IntegerField(default=0) #can be 0 or have value
transaction_date = models.DateField()
I want to query for get all customer information and date of last payment.
I have this query in postgres that is correct, is just that i need:
select e.*, max(l.transaction_date) as last_date_payment
from app_customer as e
left join app_transaction as l
on e.id = l.customer_id and l.payment_amount != 0
group by e.id
order by e.id
But i need this query in django for an serializer. I try with that but return other query
In Python:
print(Customer.objects.filter(transaction__isnull=True).order_by('id').query)
>>> SELECT app_customer.id, app_customer.name, app_customer.email, app_customer.balance FROM app_customer
LEFT OUTER JOIN app_transaction
ON (app_customer.id = app_transaction.customer_id)
WHERE app_transaction.id IS NULL
ORDER BY app_customer.id ASC
But that i need is this rows
example

Whether you are working with a serializer or not you can reuse the same view/function for both the tasks.
First to get the transaction detail for the current customer object you have you have to be aware of related_name.related_name have default values but you can mention something unique so that you remember.
Change your model:
class Transaction(models.Model):
customer = models.ForeignKey(Customer, related_name="transac_set",on_delete=models.CASCADE)
related_names are a way in django to create reverse relationship from Customer to Transaction this way you will be able to do Customer cus.transac_set.all() and it will fetch all the transaction of cus object.
Since you might have multiple customers to get transaction details for you can use select_related() when querying this will hit the database least number of times and get all the data for you.
Create a function definition to get the data of all transaction of Customers:
def get_cus_transac(cus_id_list):
#here cus_id_list is the list of ids you need to fetch
cus_transac_list = Transaction.objects.select_related(customer).filter(id__in = cus_id_list)
return cus_transac_list
For your purpose you need to use another way that is the reason you needed related_name, prefetch_related().
Create a function definition to get the data of latest transaction of Customers: ***Warning: I was typing this answer before sleeping so there is no way the latest value of transaction is being fetched here.I will add it later but you can work on similar terms and get it done this way.
def get_latest_transac(cus_id_list):
#here cus_id_list is the list of ids you need to fetch
latest_transac_list = Customer.objects.filter(id__in = cus_id_list).prefetch_related('transac_set')
return latest_transac_list
Now coming to serializer,you need to have 3 serializers (Actually you need 2 only but third one can serialize Customer data + latest transaction that you need) ...one for Transaction and another for customer then the 3rd Serializer to combine them.
There might be some mistakes in code or i might have missed some details.As i have not checked it.I am assuming you know how to make serializers and views for the same.

One approach is to use subqueries:
transaction_subquery = Transaction.objects.filter(
customer=OuterRef('pk'), payment_amount__gt=0,
).order_by('-transaction_date')
Customer.objects.annotate(
last_date_payment=Subquery(
transaction_subquery.values('transaction_date')[:1]
)
)
This will get all customer data, and annotate with their last transaction date that has payment_amount as non-zero, in one query.

To solve your problem:
I want to query for get all customer information and date of last payment.
You can try use order by combine with distinct:
Customer.objects.prefetch_related('transaction_set').values('id', 'name', 'email', 'authorized_credit', 'balance', 'transaction__transaction_date').order_by('-transaction__transaction_date').distinct('transaction__transaction_date')
Note:
It only applies to PostgreSQL when distinct followed by parameters.
Usage of distinct: https://docs.djangoproject.com/en/3.2/ref/models/querysets/#distinct

Related

Django Get Last Object for each Value in List

I have a model called Purchase, with two fields, User and amount_spent.
This is models.py:
class Purchase(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
amount_spent = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
I want to get the last purchases from a list of users.
On views.py I have a list with some User's objects, and I want to get the last purchase for each user in the list. I can't find a way of doing this in a single query, I checked the latest() operator on QuerySets, but it only returns one object.
This is views.py:
purchases = Purchase.objects.filter(user__in=list_of_users)
# purchases contains all the purchases from users, now I need to get the most recent onces for each user.
I now I could group the purchases by user and then get the most recent ones, but I was wondering it there is a way of making this as a single query to DB.
try this:
Purchase.objects.filter(user__in=list_of_users).values("user_id", "amount_spent").order_by("-id").distinct("user_id")
You can annotate the Users with the last_purchase_pks and then fetch these and adds that to these users:
from django.db.models import OuterRef, Subquery
users = User.objects.annotate(
last_purchase_pk=Subquery(
purchase.objects.order_by('-created_at')
.filter(user_id=OuterRef('pk'))
.values('pk')[:1]
)
)
purchases = {
p.pk: p
for p in Purchase.objects.filter(
pk__in=[user.last_purchase_pk for user in users]
)
}
for user in users:
user.last_purchase = purchases.get(user.last_purchase_pk)
After this code snippet, the User objects in users will all have a last_purchase attribute that contains the last Purchase for that user, or None in case there is no such purchase.

Django subquery, annotate. Is there any way to join three tables for querying to find the aggregate sum of a field in django (query set api)?

I am a complete beginner in django. I'm stuck with this problem and can't seem to find a solution to query it properly. (T-T)
django ver : 1.11 python ver : 2.7
Let's say we have 3 models:
class Coupon(models.Model):
pass
class CouponApplication(models.Model):
"""
Track application of a coupon.
"""
coupon = models.ForeignKey(Coupon, related_name="applications",verbose_name=("Coupon"))
order = models.ForeignKey(Order, verbose_name=("Order")related_name='order_coupon_applications')
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_order', verbose_name="Customer")
order_date = models.DateTimeField(verbose_name='Order Date and Time')
shop = models.ForeignKey(Location, null=True, on_delete=models.SET_NULL)
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='order_items')
order_type = models.CharField('Type', choices=ORDER_ITEM_TYPE, default=INVENTORY)
buy_get_discount = models.DecimalField(_('Buy & Get Discount'))
sub_total = models.DecimalField(_('Sub Total'), max_digits=18, decimal_places=2)
Now for each coupon, I need to find the gross sales of the products, sold using it. I am finding it pretty hard to write a query for the same because I need to annotate the sum of subtotal of order item to each coupon object and I guess this requires joining coupon application, order item and order table .
So far I've written this much:
buy_gets_gross_sales = OrderItem.objects.filter(order_type=OrderItem.INVENTORY,
buy_get_discount__gt=0,
order_id__in=CouponApplication.objects.filter(coupon=OuterRef('pk'),
).values('order')
).values('order_type')
gross_sales = buy_gets_gross_sales.annotate(gross=Sum('sub_total')).values('gross')
gross_Sales_report = Coupon.objects.annotate(
total_gross_sales =Round(Coalesce(Subquery(gross_sales, output_field=models.DecimalField()), Value(0))) ,)
i know this wont work, but i need something of this sort. it is already throwing some errors saying the inner query need to be used in a subquery or something.I need the aggregate value to be annotated with the coupon objects.
I've added the raw sql query below too:
'(select coalesce(sum(sub_total),0) from order_orderitem'
' where order_type=%(order_type)s and buy_get_discount > %(discount)s'
'and order_id in (select distinct(order_id) from coupon_couponapplication where coupon_id=t1.id '
'{0}'
'and date_created between %(start)s and %(end)s' # comment this line to remove date filter
')) '
Here t1 is Coupon model.
Help, I am pretty sure it is possible to write django query for this one, but as I said I am not able to figure it out.
Please help me to find a solution, thanks in advance. <3

How can I filter data from a table with a Foreign Key?

I have the following models:
#- coding: utf-8 -#
STATUSES = (
(u'R', u'Solicitada'), # Request
(u'V', u'VĂ¡lida'), # Deposit
(u'P', u'Imprimiendo'), # Printing
(u'S', u'Por Entregar'), # Stock
(u'D', u'Entregada'), # Delivered
(u'F', u'Olvidadas') # Forget
)
class Order(models.Model):
requester = models.CharField(max_length=128)
class OrderStatus(models.Model):
order = models.ForeignKey(Order, related_name="statuses")
status = models.CharField(max_length=1,choices=STATUSES)
date = models.DateTimeField()
I need to filter the Orders by status and by date. I've tried the following:
orders_sold = Order.objects.all()
orders_sold = orders_sold.filter(statuses__status='V')
But it returns whatever it wants, and with the date, I've tried:
orders_sold = orders_sold.filter(statuses__date__range=[from_date,to_date])
But it says that "date" doesn't exists.
How can I achieve what I'm looking to do?
UPDATE
The error that "date" gives is this:
Cannot resolve keyword 'date' into field. Choices are: added, additionalinfoorder, email, id, orderitems, password, payment, payment_date, payments, phone, print_orders, requester, statuses, taker, viewed
See this blog post; querying on reverse lookups is not exactly a supported feature of Django. You'll have to take a different approach. Here's an idea -
order_with_status_v = OrderStatus.objects.filter(status='V',date__range=[from_date,to_date]).values('order')
Now, a caveat of this is that .values() gathers the values from the SQL relation and stores it in a dictionary; the ID of the foreign key is stored, but other information is lost. So, whatever information you'd want to access must be listed in the args for values:
.values('order', 'order__requester')
In order to do this in a scant 2 db hits, try the following:
# list of the PK of all orders referenced by any OrderStatus with status "V"
order_statuses_v = OrderStatus.objects.filter(status='V',date__range=[from_date,to_date]).values_list('order__pk',flat=True)
# All orders referenced by OrderStatus with status "V"
orders_sold = Order.objects.filter(pk__in=order_statuses_v)
First, your date field it seems to be empty. You could make it save date when first created an OrderStatus object:
date = models.DateTimeField(auto_now_add=True)
Now that you have a date you can filter by status and by date:
orders_sold = Order.objects.filter(statuses__status='V', statuses__date__range=(from_date,to_date))

How to filter queryset not equal to in Django?

I have models called Stores and SaleItems which look some what like this.
class Stores(models.Model):
name = models.CharField(unique=True, max_length=20)
class SaleItems(models.Model):
sale_by = models.ForeignKey(Stores)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
So I need to retrieve sale items based on the following conditions.
If its not my store, then leave out items having start_date greater than today and end_date lesser than today.
If its my store, then get items irrespective of start_date and end_date.
So in my views.py, this is how far I have come up.
class SaleItemsView(viewsets.ModelViewSet):
querys = SaleItems.objects.all()
def get_queryset(self):
#get my store id
store_id = self.request.query_params.get('store_id')
querys = SaleItems.objects\
.exclude(store__ne=store_id, end_date__lt=timezone.now())\
.exclude(store__ne=store_id, start_date__gt=timezone.now())
return querys
But it seems django doesn't have not equal operator any more. Is there any other way I can achieve this?
I think you would combine the records for each of your conditions using Qobjects, then do distinct() to remove duplicates:
now = timezone.now()
items_within_date_range = Q(start_date__lte=today, end_date__gte=today)
curr_store_items = Q(store=store_id)
result = SaleItems.objects.filter(items_within_date_range|curr_store_items).distinct()
The first query would get all items within time range for both your store and other stores, second query would get all items for your store, then combine then would get everything.

Django: Creating a non-destinct union of two querysets

I am writing an accouting app in django and there are Orders, which have a date when the invoice was created and an optional date when a credit note is created.
class Order(models.Model):
date_invoice_created = models.DateTimeField(null=True, blank=True)
date_credit_note_created = models.DateTimeField(null=True, blank=True)
I'm currently developing the view for our accountant, and she'd like to have both the invoice and the credit note on separate rows in the admin panel, sorted by theirs respective creation dates.
So basically I'd like to show the same model twice, in different row, sorted by different fields. In SQL, this would be something like:
SELECT id, create_date FROM (
SELECT id, date_invoice_created AS create_date, 'invoice' AS type FROM order
UNION
SELECT id, date_credit_note_created AS create_date, 'creditnote' AS type FROM order
) ORDER BY create_date
Don't mind my SQL-fu not being up-to-date, but I guess you understand what I mean.
So I've tried to get django to do this for me, by overriding the date in the second queryset, because django does not support the union of two extra'd querysets:
invoices = Order.objects.filter(date_invoice_created__isnull=False)
credit_notes = Order.filter_valid_orders(qs
).filter(
date_credit_note_created__isnull=False
).extra(
select={'date_invoice_created': 'date_credit_note_created'}
)
return (invoices | credit_notes).order_by('date_invoice_created')
unfortunately, the bit-wise-or operation for union always makes sure that the IDs are distinct, but I really want them not to be. How can I achieve to have a union with duplicate rows?
I have now found the solution to my problem using a SQL-View.
I've created a new migration (using south), which contains the above SQL query mentioned in the question as a view, which returns all rows twice, each with a create_date and type respectively for the credit note and the invoice.
accounting/migrations/00xx_create_invoice_creditnote_view.py:
class Migration(SchemaMigration):
def forwards(self, orm):
query = """
CREATE VIEW invoiceoverview_invoicecreditnoteunion AS
SELECT * FROM (
SELECT *,
date_invoice_created AS create_date,
'invoice' AS type
FROM accounting_order
WHERE date_invoice_created NOT NULL
UNION
SELECT *,
date_credit_note_created AS date,
'creditnote' AS type
FROM accounting_order
WHERE date_credit_note_created NOT NULL
);
"""
db.execute(query)
def backwards(self, orm):
query = """
DROP VIEW invoiceoverview_invoicecreditnoteunion;
"""
db.execute(query)
# ...
# the rest of the migration model
# ...
Then I've created a new model for this view, which has the Meta managed = False so that django uses the model without caring about it's creation. It has all the same fields as the original Order model, but also includes the two new fields from the SQL-View:
invoiceoverview/models.py:
class InvoiceCreditNoteUnion(models.Model):
""" This class is a SQL-view to Order, so that the credit note and
invoice can be displayed independently.
"""
class Meta:
managed = False # do not manage the model in the DB
# fields of the view
date = models.DateTimeField()
type = models.CharField(max_length=255)
# ...
# all the other fields of the original Order
# ...
Now I can use this model for the contrib.admin.ModelAdmin and display the appripriate content by checking the type field. e.g.:
class InvoiceAdmin(admin.ModelAdmin):
list_display = ['some_special_case']
def some_special_case(self, obj):
if obj.type == 'creditnote':
return obj.credit_note_specific field
else:
return obj.invoice_specific_field
admin.site.register(InvoiceCreditNoteUnion, InvoiceAdmin)
This finally allows me to use all the other features provided by the admin-panel, e.g. overriding the queryset method, sorting etc.

Categories