Django query set filter - sql

I'm trying to create a query set that filters all the cars hired by a user.
the car hire model has a foreignkey which stores the user's ID when a car is hired
My current solution is like this, where I get current users ID and then try filtering the Cars database against the user's ID.
view.py:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(user_id__in=current_users_id)
args =
{
'car_hired': car_hired,
}
Models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.user.id)
class car_hired(models.Model):
car = models.ForeignKey('Car', on_delete=models.CASCADE)
customer = models.ForeignKey('UserProfile', on_delete=models.CASCADE)
start_date = models.DateField(help_text='Date of booking')
end_time = models.DateField(help_text='Date of booking')
def __str__(self):
return str(self.id)
However, I can't seem to get this to work and I am getting the error "'int' object is not iterable"
I would like to create a query set that returns the row of data that matches the query
for example, if the current user's id is 10 I would like to get all the cars hired by the user. All the data stored in the rows where car_hirer_is == 10

This part is causing the error:
...(user_id__in=current_users_id)
When you add the __in part it makes django think that current_users_id is an iterable but current_users_id = request.user.id is a single id. To make it work change it to:
def view_hire(request):
current_users_id = request.user.id
car_hired = Cars_hired.objects.filter(customer__user_id=current_users_id)
I would also reconsider the names of the variables (current_user_id instead of current_users_id and cars_hired instead of car_hired).

Related

Django annotate multiple aggregators over grouped values

Due to the structure of my project, I need to have multiple aggregations over three interlocked tables. With structure looking somewhat like this:
class ItemMeta(models.Model):
item = models.ForeignKey(
Item, on_delete=models.SET_NULL, null=True
)
class = models.CharField(max_length=2048, null=True, blank=True)
department = models.CharField(max_length=2048, null=True, blank=True)
class Item(models.Model):
amount = models.DecimalField(max_digits=18, decimal_places=2)
status = models.CharField(max_length=2048, null=True, blank=True, choices=ItemStatus.choices)
class CartItem(models.Model):
author = models.ForeignKey(to=UserModel, on_delete=model.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
class ItemStatus(models.TextChoices):
UNDER_CONSIDERATION = 'UNDER_CONSIDERATION', 'Under consideration'
IMPOSED = 'IMPOSED', 'Imposed'
PAID = 'PAID', 'Paid'
And I need to have item grouping by class, department and status both in cart and outside of it. I also need to have aggregations of combined amounts of items in different statuses, as well as counts of different items in cart and existing. So the structure of the response has to always contain 5 values: sum of paid, imposed and considered items, and count of items existing and in cart of the calling user. I inherited from last poor sod this piece of code to do these:
def _sum(self, status):
return Coalesce(Sum('amount', filter=Q(status=status)), 0.0, output_field=FloatField())
def annotate_kwargs(self):
return {
'under_consideration_amount': self._sum(ItemStatus.UNDER_CONSIDERATION),
'imposed_amount': self._sum(ItemStatus.IMPOSED),
'paid_amount': self._sum(ItemStatus.PAID),
'count': Count('pk', distinct=True),
'in_cart': Count('pk', distinct=True, filter=Q(cartitem__author=self.user)),
}
def get(self):
return self.queryset \
.values(*self.group_by) \
.annotate(**self.annotate_kwargs())
Which basically takes the Item queryset and groupes it according to request and then annotates it. Problem is, it returns lies, as is highlighted in the docs. Methinks having 3 different tables has something to do with it, but at this point i have no way to change the model structure, so it has to stay as it is or have as little change as possible. My question is how to have these aggregations? I tried using subquery, but i don't know how to make it work with .values clause

Django conditional querysets

Alright,
So I'm using a general listview showing all orders where the payment hasn't been completed yet. Let's call them outstanding orders, orders which still require some sort of payment.
Orders contain one or multiple items and can contain zero or more payments.
So I want to compare the total order value, compare this value with the total payment and if these or not equal to zero, show them in the list.
Is there someway I can build a new queryset for items which do not meet a certain condition?
views.py
class OutstandingOrderListView(ListView):
model = Order
def get_queryset(self):
queryset = Order.objects.all()[:5]
for record in queryset.iterator():
# Retrieve order_total
order_total = record.item_set.aggregate(total=Sum('price'))['total']
# Retrieve payment_total
payment_total = record.payment_set.aggregate(total=Sum('amount'))['total']
# Compare both
delta = order_total - payment_total
if delta != 0:
print("These guys owe you money!")
# Add record to new queryset?
models.py
class Order(models.Model):
no = models.CharField(max_length=9, default=increment_order_number,
editable=False, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL, null=True, related_name='created_by')
class Item(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
name = models.CharField(max_length=200, default="", blank=True)
price = models.DecimalField(max_digits=5,
decimal_places=2, default=Decimal('000.00'))
class Payment(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
date = models.DateField(default=now)
amount = models.DecimalField(max_digits=5, decimal_places=2,
default=Decimal('000.00'))
Tried to use filtering on annotations like #bdbd mentioned in the comments, in this case we could use exclude():
queryset = Order.objects.annotate(order_total=Sum('item__price')) \
.annotate(payment_total=Sum('payment__amount')) \
.exclude(order_total=F('payment_total'))[:5]
Even though this works, the second annotation shows very unusual results, not sure why but it looks like a multiple from order_total...

How to properly design a database for rating system with multiple rating criterias

I am doing rating system on my current django project and I need to implement database for rating. Database would be simple, if I didn't have multiple rating criteries, now I have to ask:
How would you design a database with these objects:
User
RatedObject
Rating
there will ofcourse be multiple users and objects to be rated AND multiple rating criterias.
Now my current idea would be to go for few separate tables with each of the objects such as:
USER(pk=id, fk= rating.id)
RATED_OBJECT(pk=id, fk= rating.id, overall_attribute_1, overall_attribute_2, overall_rating)
RATING(pk=user.id, pk=rated_object.id, fk=rated_attribute1, fk=rated_attribute2)
RATED_ATRIBUTE(pk=id, fk=type, value)
TYPE(pk=id, name) - 2 types since we have 2 attributes to be rated
(now overal_rating will be average of all overall attributes and each overal attribute will be average of all attributes of one type from ratings, where the id of rated object will be same)
I have a bad feeling about doing this 'multiple-FK-to-one-PK' operation. Would it make more sence to make table for each rated attribute? Or maybe say **** it and have values in the RATING itself and screw RATED_ATTRIBUTE and TYPE table? What do you guys think?
EDIT: IF my guess was right- meaning I could connect RATING with RATED_ATTRIBUTE multiple times (multiple fk to 1 pk), I need to set constraints, so when rating happens, it all goes to fields of RATING correctly
(when rating attribute one, in RATING i need to ensure that in RATED_ATTRIBUTE the type is exactly the one like in RATING). Now, I cannot get my head arround, how to do that whidj Django's CheckConstraint, any ideas?
snippet:
class RatedAttribute(models.Model):
TYPE = (
('attribute1', 'attribute1'),
('attribute2', 'attribute2'),
)
id = models.IntegerField(primary_key= True, auto_created=True)
type = models.CharField(max_length=120, choices=TYPE, default=None)
value = models.IntegerField()
class Rating(models.Model):
user_id = models.ManyToManyField(User, through='RatingUser')
rated_object_id = models.ManyToManyField(RatedObject, through='Rating_Object')
attribute1 = models.ForeignKey(RatedAttribute, on_delete=models.CASCADE)
attribute2 = models.ForeignKey(RatedAttribute, on_delete=models.CASCADE)
class Meta:
constraints = [
CheckConstraint(
*NO IDEA HOW TO FILL THIS CONSTRAINT(s)*, name='something')
]
def __str__(self):
return self.place
code is with proper connecting Rating_Object
This is what i came up with after reading what you put in the comments.
If anything comment on this answer.
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
rating_choices = [
(1, '1 Star'),
(2, '2 Stars'),
(3, '3 Stars'),
(4, '4 Stars'),
(5, '5 Stars'),
]
class Product(models.Model):
class Meta:
verbose_name = "Product"
verbose_name_plural = "Products"
name = models.CharField(max_length=50)
description = models.TextField()
def __str__(self):
return self.name
class Rating(models.Model):
class Meta:
verbose_name = "rating"
verbose_name_plural = "ratings"
user = models.ForeignKey(User, verbose_name=_(""), on_delete=models.CASCADE, related_name="rating")
product = models.ForeignKey(Product, verbose_name=_(""), on_delete=models.CASCADE, related_name="product_rating")
taste = models.IntegerField(choices=rating_choices)
smell = models.IntegerField(choices=rating_choices)
def __str__(self):
return "{self.user.username}'s rating for {self.product.name}"

Django complex filter and order

I have 4 model like this
class Site(models.Model):
name = models.CharField(max_length=200)
def get_lowest_price(self, mm_date):
'''This method returns lowest product price on a site at a particular date'''
class Category(models.Model):
name = models.CharField(max_length=200)
site = models.ForeignKey(Site)
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category)
class Price(models.Model):
date = models.DateField()
price = models.IntegerField()
product = models.ForeignKey(Product)
Here every have many category, every category have many product. Now product price can change every day so price model will hold the product price and date.
My problem is I want list of site filter by price range. This price range will depends on the get_lowest_price method and can be sort Min to Max and Max to Min. Already I've used lambda expression to do that but I think it's not appropriate
sorted(Site.objects.all(), key=lambda x: x.get_lowest_price(the_date))
Also I can get all site within a price range by running a loop but this is also not a good idea. Please help my someone to do the query in right manner.
If you still need more clear view of the question please see the first comment from "Ishtiaque Khan", his assumption is 100% right.
*In these models writing frequency is low and reading frequency is high.
1. Using query
If you just wanna query using a specific date. Here is how:
q = Site.objects.filter(category__product__price__date=mm_date) \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
It will return a list of Site with lowest price on mm_date fall within range of min_price - max_price. You can also query for multiple date using query like so:
q = Site.objects.values('name', 'category__product__price__date') \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
2. Eager/pre-calculation, you can use post_save signal. Since the write frequency is low this will not be expensive
Create another Table to hold lowest prices per date. Like this:
class LowestPrice(models.Model):
date = models.DateField()
site = models.ForeignKey(Site)
lowest_price = models.IntegerField(default=0)
Use post_save signal to calculate and update this every time there. Sample code (not tested)
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Price)
def update_price(sender, instance, **kwargs):
cur_price = LowestPrice.objects.filter(site=instance.product.category.site, date=instance.date).first()
if not cur_price:
new_price = LowestPrice()
new_price.site = instance.product.category.site
new_price.date = instance.date
else:
new_price = cur_price
# update price only if needed
if instance.price<new_price.lowest_price:
new_price.lowest_price = instance.price
new_price.save()
Then just query directly from this table when needed:
LowestPrice.objects.filter(date=mm_date, lowest_price__gte=min_price, lowest_price__lte=max_price)
Solution:
from django.db.models import Min
Site.objects.annotate(
price_min=Min('categories__products__prices__price')
).filter(
categories__products__prices__date=the_date,
).distinct().order_by('price_min') # prefix '-' for descending order
For this to work, you need to modify the models by adding a related_name attribute to the ForeignKey fields.
Like this -
class Category(models.Model):
# rest of the fields
site = models.ForeignKey(Site, related_name='categories')
Similary, for Product and Price models, add related_name as products and prices in the ForeignKey fields.
Explanation:
Starting with related_name, it describes the reverse relation from one model to another.
After the reverse relationship is setup, you can use them to inner join the tables.
You can use the reverse relationships to get the price of a product of a category on a site and annotate the min price, filtered by the_date. I have used the annotated value to order by min price of the product, in ascending order. You can use '-' as a prefix character to do in descending order.
Do it with django queryset operations
Price.objects.all().order_by('price') #add [0] for only the first object
or
Price.objects.all().order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('-price') #add [0] for only the first object
I think this ORM query could do the job ...
from django.db.models import Min
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('price_min')
or /and for reversing the order :
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('-price_min')

Django annotate code

Just stumbled upon some guy code
He have models like this
class Country(models.Model):
name = models.CharField(max_length=100)
class TourDate(models.Model):
artist = models.ForeignKey("Artist")
date = models.DateField()
country = models.ForeignKey("Country")
And is querying like this
ireland = Country.objects.get(name="Ireland")
artists = Artist.objects.all().extra(select = {
"tourdate_count" : """
SELECT COUNT(*)
FROM sandbox_tourdate
JOIN sandbox_country on sandbox_tourdate.country_id = sandbox_country.id
WHERE sandbox_tourdate.artist_id = sandbox_artist.id
AND sandbox_tourdate.country_id = %d """ % ireland.pk,
}).order_by("-tourdate_count",)
My question is why He have underscores like sandbox_tourdate but it isn't in model field
Is that created automatically like some sort of pseudo-field?
sandbox_tourdate isn't the name of the field, it's the name of the table. Django's naming convention is to use appname_modelname as the table name, although this can be overridden. In this case, I guess the app is called 'sandbox'.
I don't really know why that person has used a raw query though, that is quite easily expressed in Django's ORM syntax.