class Review(models.Model):
slug = models.SlugField(max_length=255, unique=True)
vendor = models.ForeignKey(Vendor)
user = models.ForeignKey(User, blank=True, null=True)
product = models.ForeignKey(Product, blank=True, null=True)
images = models.ManyToManyField(ReviewImage, blank=True, null=True)
headline = models.CharField(max_length=100)
review = models.TextField(blank=True, null=True)
rating = models.IntegerField()
active = models.BooleanField(default=1)
created = models.DateTimeField(auto_now_add=True)
changed = models.DateTimeField(auto_now=True)
# This is the problem... I works, but no vendor is shown if there is no review.
vendor_list = (Vendor.objects.filter(category=category,
review__product__isnull=True,
active=True)
.annotate(rating_avg=Avg('review__rating')))
HOW can I do it with review__product__isnull=True? If there is no review at all, I still want the vendor, but the rating should be: "0", .. what to do?
Let's see if I understand this. You are trying to list all active vendors in the category, annotated with the average rating of their reviews. The way you determine that a review is a vendor review rather than a product review is that the product field is null. And you want the average rating of vendors with no reviews to be zero.
In SQL your query requires an OUTER JOIN:
SELECT vendor.id, COALESCE(AVG(review.rating), 0.0) AS rating
FROM myapp_vendor AS vendor
LEFT OUTER JOIN myapp_review AS review
ON review.vendor_id = vendor.id
AND review.product IS NULL
WHERE vendor.category = %s
AND vendor.active
GROUP BY vendor.id
Sometimes in Django the simplest solution is a raw SQL query: as the developers say, the database API is "a shortcut but not necessarily an end-all-be-all." So that would look like this:
for v in Vendor.objects.raw('SELECT ... ', [category]): # query as above
print 'Vendor {0} has rating {1}'.format(v.name, v.rating)
OK I might be wrong here. I did a small test and it gave me the correct result but I would have to spend more time testing and I don't have that now.
You could try this:
vendor_list = Vendor.objects.filter(category=category, active=True)
vendor_list = vendor_list.filter(Q(review__product__isnull=True)|Q(review__isnull=True)).annotate(rating_avg=Avg('review__rating'))
(The filter has been separated in to 2 lines to make it easier to read but could be merged)
The idea is that you first take all vendors and then filter those who either has no product reviews or no reviews at all. Then you annotate those.
The rating for those vendors missing a review would be None not 0.
Related
I'm using Django==3.2 and Django-rest-framework==3.12.4 . I have two modals with following names Product and ProductRating.
As you can see below:
class Product(models.Model):
user = models.ForeignKey(User,related_name='userprofile', on_delete=models.CASCADE)
name = models.CharField(("name"), max_length=50,null=True, blank=True)
price = models.IntegerField(_("price"),default=0)
create_time = models.DateTimeField(_("Create time"), default=timezone.now)
class ProductRating(models.Model):
user = models.ForeignKey(User, verbose_name=_("user"), on_delete=models.CASCADE,null=True,blank=True)
product = models.ForeignKey(Product, verbose_name=_("product"), on_delete=models.CASCADE)
stars = models.IntegerField(_("stars"),default=0)
is_remove = models.BooleanField(_("is_remove"),default=False)
create_time = models.DateTimeField(_("Create time"), default=timezone.now)
Now I want to get the top rated products. According to the average rating of each product. the product that has highest rating and most count of reviews should appear first in the query list.
I have tried the following thing but that just gave me those products who has any rating element.
as you can see below.
def get_all_top_rated_projects(self):
query = self.filter(productrating__isnull=False)
print( query[0].productrating.object.all())
I know its quite tricky case. I have tried to do it by myself. But i'm unable to do that. Anyone expert You can help and pull me out from this situation.
You can use Avg database function
from django.db.models import Avg, Q
Product.objects.annotate(avg_rate=Avg("product__stars", filter=Q(product__is_remove=False))).order_by("avg_rate")
I'm currently working on a website where advertisements will be posted to display vehicles for sale and rent. I would like to retrieve a queryset that highlights only one car brand (i.e. Audi) which has the highest number of posts for the respective model. Example:
Displaying the Audi brand because it has the highest number of related posts.
My question is, what's the most efficient way of doing this? I've done some work here but I'm pretty sure this is not the most efficient way. What I have is the following:
# Algorithm that is currently retrieving the name of the brand and the number of related posts it has.
def top_brand_ads():
queryset = Advertisement.objects.filter(status__iexact="Published", owner__payment_made="True").order_by('-publish', 'name')
result = {}
for ad in queryset:
# Try to update an existing key-value pair
try:
count = result[ad.brand.name.title()]
result[ad.brand.name.title()] = count + 1
except KeyError:
# If the key doesn't exist then create it
result[ad.brand.name.title()] = 1
# Getting the brand with the highest number of posts from the result dictionary
top_brand = max(result, key=lambda x: result[x]) # Returns for i.e. (Mercedes Benz)
context = {
top_brand: result[top_brand] # Retrieving the value for the top_brand from the result dict.
}
print(context) # {'Mercedes Benz': 7} -> Mercedes Benz has seven (7) related posts.
return context
Is there a way I could return a queryset instead without doing what I did here or could this be way more efficient?
If the related models are needed, please see below:
models.py
# Brand
class Brand(models.Model):
name = models.CharField(max_length=255, unique=True)
image = models.ImageField(upload_to='brand_logos/', null=True, blank=True)
slug = models.SlugField(max_length=250, unique=True)
...
# Methods
# Owner
class Owner(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
telephone = models.CharField(max_length=30, blank=True, null=True)
alternate_telephone = models.CharField(max_length=30, blank=True, null=True)
user_type = models.CharField(max_length=50, blank=True, null=True)
payment_made = models.BooleanField(default=False)
expiring = models.DateTimeField(default=timezone.now)
...
# Methods
# Advertisement (Post)
class Advertisement(models.Model):
STATUS_CHOICES = (
('Draft', 'Draft'),
('Published', 'Published'),
)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=150, blank=True, null=True)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Draft')
...
# Other fields & methods
Any help would be greatly appreciated.
Since you need brands, let's query on Brand model:
Brand.objects.filter(advertisement__status__iexact="Published").\
filter(advertisement__owner__payment_made=True).\
annotate(published_ads=Count('advertisement__id')).\
order_by('-published_ads')
However, even in your proposed solution, you can improve a little bit:
Remove the order_by method from your queryset. It doesn't affect the final result but adds some overhead, especially if your Advertisement model is not indexed on those fields.
Every time you call ad.brand you are hitting the database. This is called the N+1 problem. You are in a loop of n, you make n extra db access. You can use select_related to avoid such problems. In your case: Advertisement.objects.select_related('brand')...
Did you try the count method?
from django.db.models import Count
Car.objects.annotate(num_views=Count('car_posts_related_name')).order_by('num_views')
I have two models:
class City(models.Model):
name = models.CharField(max_length=50, verbose_name='Qyteti')
slug = models.SlugField(unique=True)
class Business(models.Model):
name = models.CharField(max_length=120, verbose_name='emri')
slug = models.SlugField(unique=True)
city = models.OneToOneField(City, verbose_name='qyteti')
created = models.DateTimeField(auto_now_add=True, verbose_name='krijuar')
categories = models.ForeignKey(Category, related_name='businesses', verbose_name='kategoria')
user = models.ForeignKey(User, related_name='user_businesses', verbose_name='autori')
geom = gis_models.PointField(u"longitude/latitude", geography=True, blank=True, null=True)
I want to create a serach like yelp.com
I want people to search in three different ways.
One type of business in all cities.
All type of businesses in one city.
One type of business in one city.
I've tried chain from itertools, but no results so far.
I'd like to do just a simple search for now, not with external search engines.
Anyone's help is appreciated.
You need to do the following:
1) Change in your model:
class Business(models.Model):
city = models.ForeignKey(City, verbose_name='qyteti', related_name='businesses')
2) Queries:
1) one_type = Business.objects.filter(name = "some_business_type").select_related('city')
one_type.city.name
2) one_city_all_business = City.objects.filter(name = "London").prefetch_related('businesses')
one_city_all_business.businesses.all() - here u get a list of all businesses for London
3) one_type_one_city = City.objects.filter(name = "London").filter(businesses__name = "some_business_type").prefetch_related('businesses')
The only problem here - you need to decide how user will select what query to run, I mean how will you define what user wants to search for.
I need to use different order_by & distinct values, and I have made an attempt using a subquery.
How can I achieve this?
Could a qset select the Products I want, and then in a separate query, select the 15 Variations whose price you want to display?
In other words: Qset randomly selects product ID's (in a queryset), then python tells it to return a queryset of just those 15 items.
Speeding up the query too is important- as it takes ~800ms (when I order_by the pk) or 5.8seconds when I use order_by '?'.
My attempt:
distinct_qs = (
Product.objects
.distinct('id')
)
qset = (
Product.objects
.filter(pk__in=distinct_qs)
.order_by('rating', '?')
.values('name', 'image',)
.annotate(
price=F('variation__price__price'),
id=F('pk'),
vari=F('variation'),
)[:15]
)
Sample of output data:
{"name":"Test Item","vari":10, id":1, "price":"80", "image":"xyz.com/1.jpg"},
{"name":"Test Item","vari":11, id":1, "price":"80", "image":"xyz.com/1.jpg"},
{"name":"Another one","vari":14, id":2, "price":"10", "image":"xyz.com/2.jpg"},
{"name":"Another one","vari":15, id":2, "price":"10", "image":"xyz.com/2.jpg"},
{"name":"And Again","vari":17, id":3, "price":"12", "image":"xyz.com/3.jpg"},
{"name":"And Again","vari":18, id":3, "price":"12", "image":"xyz.com/3.jpg"},
Desired output data:
{"name":"Test Item","vari":13, id":1, "price":"80", "image":"xyz.com/1.jpg"},
{"name":"Another one","vari":14, id":2, "price":"10", "image":"xyz.com/2.jpg"},
{"name":"And Again","vari":17, id":3, "price":"12", "image":"xyz.com/3.jpg"},
Sample of models.py
class Product(models.Model):
name = models.CharField ("Name", max_length=400)
...
class Variation(models.Model):
product = models.ForeignKey(Product, db_index=True, blank=False, null=False)
...
class Image(models.Model):
variation = models.ForeignKey(Variation, blank=False, null=False)
image = models.URLField(max_length=540, blank=True, null=True)
class Price(models.Model):
price = models.DecimalField("Price", decimal_places=2, max_digits=10)
variation = models.ForeignKey(Variation, blank=False, null=False)
I think you should write a custom model manager (see https://docs.djangoproject.com/en/1.9/topics/db/managers/ ) and create a method there which you then would use for returning variations instead of a standard query.
For randomising you could do like this:
select the last id of Variation (or Product), then generate different random 15 ids from that interval and then just pull objects with those ids from database. I think it should work faster.
I am new to Django and python and I have been following the Django tutorial to build a personal project.
So far I have been able to do it without major problems, but have got stuck in the creation of the models.py when I need more than one table and need to build relationship with them.
I have two tables: one related to a product list and another one related to average prices for those types of products
I basically want to get the records of table 2 and add the average price for them which is stored in table 1, and the join must be done using 3 fields: model, year and size.
When I query only the products table (2), I do it as
latest_products = Products.objects.all().order_by('-date_added')
How could I get also the average price for each product from the average price table inside this query?
I have already read the documentation and many posts here but still have this mess in my
table 1 (average price)
class Avg_price(models.Model):
model = models.CharField(max_length=50, blank=True)
size= models.IntegerField(blank=True, null=True)
year= models.IntegerField(blank=True, null=True)
price= models.IntegerField(blank=True, null=True)
table 2 (product list)
class Products(models.Model):
product_id =models.IntegerField(blank=True, null=True)
location = models.CharField(max_length=200, blank=True)
date_added= models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=50, blank=True)
model = models.CharField(max_length=50, blank=True)
size= models.IntegerField(blank=True, null=True)
year= models.IntegerField(blank=True, null=True)
price= models.IntegerField(blank=True, null=True)
avg_price=models.ForeignKey(Avg_price)
Sorry if this questions might sound dumb... and thanks for any help!
Once you have a Products instance, you also have their related Avg_price. Check this out:
>>> my_product = latest_products[0]
>>> print my_product.avg_price.year
"prints my_product's price's year"
Alternatively, you could use .select_related queryset method:
products_prices = Products.objects.all().select_realted('avg_price')
Does that answer your question?